当前位置:首页 » 《随便一记》 » 正文

WebRTC媒体协商01 流程介绍

17 人参与  2024年10月22日 14:40  分类 : 《随便一记》  评论

点击全文阅读


一、媒体协商的重要性:

为了更好地说明这一点,我将通过一个小故事来解释:

想象一下,在一个战争时代,一名地下党员小李深入某个棉被厂充当的卧底。上级下达了一项刺杀任务,这引发了几个关键问题:

刺杀目标是谁?(接收者信息)哪条路最为迅速?(路线选择)应当使用何种型号的子弹? 如果我使用的是三八大盖的枪,而你送来的却是汉阳造的子弹,那么就无法使用了。(传输内容信息)在传送过程中如何确保敌人无法发现子弹? 例如,可以将子弹藏在一个带锁的箱子内。(传输加密)

通过这个三流的故事,我们可以得出结论:要想与某人进行通信,我们需要了解以下几个关键点:

接收者的位置在哪里?(IP 地址 + 端口号)哪条路径最为优化?(ICE + Candidate 选路)应当发送何种类型的“子弹”?(即呼叫者和被呼叫者的一些能力,例如对音视频编解码器的支持情况)如何确保在传输过程中“子弹”不会被发现?(即对传输内容进行加密)

通过这种“生动”的对比,媒体协商的重要性得以清晰展现。就是开始通话之前我们先互相了解下对方的条件(如:音视频编解码器、使用的传输协议、IP 端口和传输速率等等),这个条件是通过“信令服务器”以SDP的方式交换的。

二、WebRTC中媒体协商的流程:

下图中Amy是呼叫端,Bob是被呼叫端,中间Signaling Channel是一个他俩都能访问到的“信令服务器”,我们通过这个图说清楚媒体协商的过程。

在这里插入图片描述

CreateOffer:Amy创建一个本地Offer,并通过信令服务器发送给Bob;SetLocalDescription:Amy同时将创建的本地Offer保存到自己的local_description中;setRemoteDescription:对Bob来说,收到的Amy Offer信息就是远端的信息,因此保存到自己的remote_description中;CreateAnswer:Bob创建一个Answer信息;SetLocalDescription:将Answer信息存储到本地的local_description当中,并开始协商;Bob将Answer发送给Amy;SetRemoteDescription:Amy收到Bob发送过来的Answer,存储到自己的remote_description当中,并开始协商;

备注:

上面说的offer和answer都属于SDP信息,也就是一个json字符串,里面描述了自己的能力(编解码能力、协议、码率等);

上面说的“协商”就指的是根据offer和answer,提取两者都支持的最优能力;

媒体协商的最终结果就是,双方都创建了一个RTCPeerConnection(可以简单理解成一个超级socekt),后面就可以直接发送音视频数据了;

还有一个非常重要的动作,就是收集candidate;(当然是并行同时发送Offer给对端,也就是说Offer里面并没有包含candidate,因为还正在收集)

Amy收到对端Answer才开始协商,Bob在发送Answer之前就开始协商了;

三、媒体协商前端代码:

上面知道了流程,如果我们在浏览器想使用webrtc的WebAPI,应该使用什么样的流程呢?

呼叫方创建并发送offer:

var pcLocal = new RTCPeerConnection(opts1); pcLocal.createOffer((offer)=>{      pcLocal.setLocalDescription(offer);    singalChannel.send(offer)}, handleError);

被呼叫方收到offer:

var pcRemote = new RTCPeerConnection(opts2);signalChannel.on('message', (message)=>{    if(message.type === 'offer'){        pcRemote.setRemoteDescription(                new RTCSessionDescription(message)    )    }})

被呼叫方创建并发送answer:

pcRemote.createAnswer((answer)=>{    pcRemote.setLocalDescription(answer);    singalChannel.send(answer);}, handleError );

呼叫方收到answer:

signalChannel.on('message',(message)=>{    if(message.type==='answer'){        pcLocal.setRemoteDescription(                new RTCSessionDescription(message)    )    }})

四、媒体协商状态机:

为了提高媒体协商过程的可靠性、可维护性和灵活性,引入状态机管理协商状态;

1、状态转移图如下:

在这里插入图片描述

记住,每次设置完成之后,只有我们的状态机回到“Stable”状态,这样下次才可以工作;

状态机对呼叫方和被呼叫方都有一个;

状态解释:

stable:当创建完RTCPeerConnection实例之后,便处于stable状态;have-local-offer:当调用setLocalDescription之后,便成了have-local-offer状态;have-remote-offer:被呼叫段收到呼叫端发送的offer之后;have-local-pranswer:就是被呼叫端提前应答;一般是媒体数据没有准备好的时候,提前应答节约时间;那这个临时的answer有一个特点,就是它没有媒体数据也就是说没有音频流和视频流,并且将这个发送的方向设置成send only;这样,可以先进行ICE收集candidate,和进行DTLS握手;have-remote-pranswer:呼叫端收到被呼叫端发送来的pranswer之后,设置为这个状态(记住此时非stable,不能操作RTCPeerConnection);

状态机运行:

理想情况下:呼叫方(1->2->3),被呼叫方(6->7->9)

开始创建这个RTCPeerConnection的时候,处于stable状态,但是这个时候还不能进行编解码,因为没有进行媒体协商;

呼叫方创建offer,并调用setLocalDescription将offer传进去,状态变化:stable -> have-local-offer;

当被呼叫端收到offer之后,调用setRemoteDescription将offer设置进去,状态变化:stable->have-remote-offer;

被呼叫方创建answer之后,调用setLocalDescription将answer设置进去,状态变化:have-remote-offer -> stable;

呼叫方收到answer之后,调用setRemoteDescription将answer设置进去,状态变化:have-local-offer -> stable;

这时候双方都回到stable状态了,因此就可以继续使用RTCpeerConnection来进行编解码、传输等操作了;

特殊情况:呼叫方(1->2->4->5)被呼叫方(1->6->7->8)

开始创建这个RTCPeerConnection的时候,处于stable状态,但是这个时候还不能进行编解码,因为没有进行媒体协商;呼叫方创建offer,并调用setLocalDescription将offer传进去,状态变化:stable -> have-local-offer;被呼叫方收到offer之后,进行setRemoteDescription,将offer设置到本地,状态变化:stable -> have-remote-offer;被呼叫方可能因为暂未收集好媒体数据,先生成一个pranswer,状态变化:have-remote-offer -> have-local-pranswer;呼叫方收到对端的pranswer之后,调用setRemoteDescription设置到自己本地,状态变化: have-local-offer -> have-remote-pranswer;这个时候双方可以提前走后续的ICE收集candidate、dtls等流程;被呼叫方收集好媒体数据之后,生成一个answer,调用setLocalDescription设置进本地,状态变化:have-local-pranswer -> stable;呼叫方收到对端的answer之后,调用setRemoteDescription将answer设置进本地,状态变化:have-remote-pranswer -> stable:这时候双方都回到stable状态了,因此就可以继续使用RTCpeerConnection来进行编解码、传输等操作了;

2、时序图:

在这里插入图片描述

1)上面主要分为三部分:协商部分、candidate收集部分、数据流部分

2)呼叫段发送Offer的同时,也会去向sturn/turn服务器发送bind request;

3)等sturn/turn收集完所有通路之后,就会通过onIceCandidate回复过来;

4)然后再A这边通过信令服务器发送candidate给B;

5)B将A的通路进行排序,然后选择最优的,然后,同时将candidate发送给A;

6)A收到之后也检测下连通性;

7)然后就通过P2P去穿越发送媒体流给B;

8)B收到之后不会立即显示,会通过onAddStream添加到本地,然后向上抛给渲染模块显示

五、SDP:

上面说的协商过程中发送的offer和answer都是一种SDP格式的信息,SDP 全称 SessionDescription Protocal,直译就是通用会话描述协议。

1、SDP结构:

在这里插入图片描述

会话层:基本是对全局会话的说明,信息量比较小,包括会话的名称、目的、存活时间等;媒体层:媒体层非常重要,包括媒体格式、传输协议、ip和端口(webrtc没有使用这个,使用的是ice)、媒体负载类型;格式:json格式,由多条"="组成;这儿暂时不分析,直接分析webrtc里面具体的sdp;

2、WebRTC中的SDP:

webrtc中的SDP和标准的SDP规范有所不同,主要是按照功能划分了如下五大部分:

在这里插入图片描述

3、关键字段说明:

ICE-FULL和ICE-LITE:

为了验证服务器给我的candidate是否正确,需要发送stun request,对端回复stun response;

ice-full客户端和服务端都要发送stun request,为了提高效率ice-lite只需要客户端发送即可;

一般服务器都默认是ice-lite;

PlanB和UnifiedPlan:

PlanB 和 UnifiedPlan 其实就是 WebRTC 在多路媒体源(multi media source)场景下的两种不同的 SDP 协商方式。如果引入 Stream 和 Track 的概念,那么一个 Stream 可能包含 AudioTrack 和 VideoTrack,当有多路 Stream 时,就会有更多的 Track,如果每一个 Track 唯一对应一个自己的 M 描述,那么这就是 UnifiedPlan,如果每一个 M line 描述了多个 Track(track id),那么这就是 Plan B;

PlanB比较古老,除非考虑兼容性,否则直接用 UnifiedPlan;

4、实例分析:

// 【1】会话层// sdp版本号为0v=0// owner的名字不写,会话id是7595655801978680453,会话版本是2(后面如果有类似改变编码的操作,sess-version加1),地址类型为IP4,地址为127.0.0.1(这里可以忽略)o=- 7595655801978680453 2 IN IP4 127.0.0.1// session名字为空s=-// 会话的起始时间,都为0表示一直存活t=0 0a=ice-lite// 音频、视频的传输的传输采取多路复用,在底层传输层使用同一个通道尽心传输a=group:BUNDLE 0 1// WMS是WebRTC Media Stram的缩写,这里给Media Stream定义了一个唯一的标识符。一个Media Stream可以有多个track(video track、audio track),这些track就是通过这个唯一标识符关联起来的,具体见下面的媒体行(m=)以及它对应的附加属性(a=ssrc:)a=msid-semantic: WMS 5Y2wZK8nANNAoVw6dSAHVjNxrD1ObBM2kBPV// 【2】媒体层// [2.1] 音频描述// [2.1.1] m行// 表示媒体是音频,可以向我的端口x发送数据(9其实表示这个端口无意义),采用UDP传输加密的RTP包,并使用基于RTCP的音视频feedback机制来提升传输质量,// 后面的数字是payload typem=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 126// [2.1.2]a行,对前面m行补充说明// 音频发送者的IP4地址,WebRTC采用ICE,这里的 0.0.0.0表示任意端口c=IN IP4 0.0.0.0// RTCP采用的端口、IP地址(webrtc同样没有使用)a=rtcp:9 IN IP4 0.0.0.0// ice-ufrag、ice-pwd 是ice协商时候使用,会话建立的时候我先把我的用户名密码发送给你,你建立会话之后再发送给我,// 如果我校验不是我原来的用户名和密码,我就终止会话a=ice-ufrag:58142170598604946a=ice-pwd:71696ad0528c4adb02bb40e1// 表示我使用的trickle协议,原来的是会话建立的时候,我先要将本地的所有candidate都收集完成,然后,再通过信令服务器交换给你,// 现在是SetLocalDescription将我本地的发送给对端之后,我就同时开始收集本地candidate,我收集到一个发现比之前的好,// 就替换掉之前的,循环往复;a=ice-options:trickle// 指纹,我先把我自己的指纹发送给你,待会儿通过dtls进行证书交换的时候,对方收到了证书之后,通过证书生成一个指纹,// 和之前你发送的一样的话,表示这个会话是值得信任的,否则,不进行通话;a=fingerprint:sha-256 7F:98:08:AC:17:6A:34:DB:CF:3B:EC:93:ED:57:3F:5A:9E:1F:4A:F3:DB:D5:BF:66:EE:17:58:E0:57:EC:1B:19// 表示我们谁作为服务端,谁作为客户端,由你(answer)说了算a=setup:actpass// 当前媒体行的标识符(在a=group:BUNDLE 0 1 这行里面用到,这里0表示audio)a=mid:0// RTP允许扩展首部,这里表示采用了RFC6464定义的针对audio的扩展首部,用来调节音量,比如在大型会议中,有多个音频流,就可以用这个来调整音频混流的策略// 这里没有vad=1,表示不启用这个音量控制a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level// 表示既可以发送音频,也可以接收音频a=sendrecv// 表示我支持rtp和rtcp端口复用a=rtcp-mux// 下面几行都是对audio媒体行的补充说明(针对111),包括rtpmap、rtcp-fb、fmtp// 表示我对下面将对前面payload type为111的进行说明a=rtpmap:111 opus/48000/2// rtcp-fb:基于RTCP的反馈控制机制a=rtcp-fb:111 transport-cca=rtcp-fb:111 nack// 最小的音频打包时间a=fmtp:111 minptime=20// 跟前面的rtpmap类似a=rtpmap:126 telephone-event/8000// ssrc用来对媒体进行描述,格式为a=ssrc:<ssrc-id> <attribute>:<value>,具体可参考 RFC5576// cname用来唯一标识媒体的数据源a=ssrc:16864608 cname:YZcxBwerFFm6GH69// msid后面带两个id,第一个是MediaStream的id,第二个是audio track的id(跟后面的mslabel、label对应)a=ssrc:16864608 msid:5Y2wZK8nANNAoVw6dSAHVjNxrD1ObBM2kBPV 128f4fa0-81dd-4c3a-bbcd-22e71e29d178a=ssrc:16864608 mslabel:5Y2wZK8nANNAoVw6dSAHVjNxrD1ObBM2kBPVa=ssrc:16864608 label:128f4fa0-81dd-4c3a-bbcd-22e71e29d178// 跟audio类似,不赘述m=video 9 UDP/TLS/RTP/SAVPF 122 102 125 107 124 120 123 119c=IN IP4 0.0.0.0a=rtcp:9 IN IP4 0.0.0.0a=ice-ufrag:58142170598604946a=ice-pwd:71696ad0528c4adb02bb40e1a=fingerprint:sha-256 7F:98:08:AC:17:6A:34:DB:CF:3B:EC:93:ED:57:3F:5A:9E:1F:4A:F3:DB:D5:BF:66:EE:17:58:E0:57:EC:1B:19a=setup:actpassa=mid:1a=extmap:2 urn:ietf:params:rtp-hdrext:toffseta=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-timea=extmap:4 urn:3gpp:video-orientationa=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delaya=sendrecva=rtcp-mux// 正常的rtcp包括对端带宽估计、丢包信息等,这表示可以为了节省带宽,你只发送丢了哪些包即可;a=rtcp-rsize// 表示我对下面将对前面payload type为122的进行说明a=rtpmap:122 H264/90000// 表示在 SDP 中为 RTP 负载类型为122的媒体流启用了对 FIR 的 RTCP 反馈机制,用于接收端向发送端请求发送关键帧a=rtcp-fb:122 ccm fir// 表示在 SDP 中为 RTP 负载类型为122的媒体流启用了 NACK 的 RTCP 反馈机制,用于在传输过程中检测和处理丢包情况a=rtcp-fb:122 nack// 用于在传输过程中检测丢包情况,而 PLI 是 Picture Loss Indication 的缩写,用于指示接收端需要一个关键帧(I帧)// 来正确解码和显示视频。当接收端检测到丢包或图像帧损坏时,它可以发送NACK反馈请求重新发送丢失的数据包,// 并且发送 PLI 请求以请求发送端发送一个关键帧a=rtcp-fb:122 nack pli// 表示在 SDP 中为 RTP 负载类型122配置了Google提出的 REMB 反馈机制,// 用于接收端向发送端报告估计的最大比特率,以优化传输和编码参数的调整a=rtcp-fb:122 goog-remb// 在 SDP 中为 RTP 负载类型为 96 的媒体流启用了 Transport-CC RTCP 反馈机制,用于实现传输方向上的拥塞控制a=rtcp-fb:122 transport-cca=fmtp:122 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f// 表示在 SDP 中定义了 RTP 负载类型为102的媒体流,并指定了使用 RTX 作为重传机制a=rtpmap:102 rtx/90000// 表示在 SDP 中为 RTP 负载类型为102的媒体流定义了特定的参数设置,其中指定了负载类型102是负责重传负载类型122的数据包。// 这种设置通常与 RTX(Retransmission Transmission)相关联,用于处理数据包的重传机制a=fmtp:102 apt=122a=rtpmap:125 H264/90000a=rtcp-fb:125 ccm fira=rtcp-fb:125 nacka=rtcp-fb:125 nack plia=rtcp-fb:125 goog-remba=rtcp-fb:125 transport-cca=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01fa=rtpmap:107 rtx/90000a=fmtp:107 apt=125a=rtpmap:124 H264/90000a=rtcp-fb:124 ccm fira=rtcp-fb:124 nacka=rtcp-fb:124 nack plia=rtcp-fb:124 goog-remba=rtcp-fb:124 transport-cca=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d0032a=rtpmap:120 rtx/90000a=fmtp:120 apt=124a=rtpmap:123 H264/90000a=rtcp-fb:123 ccm fira=rtcp-fb:123 nacka=rtcp-fb:123 nack plia=rtcp-fb:123 goog-remba=rtcp-fb:123 transport-cca=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032a=rtpmap:119 rtx/90000a=fmtp:119 apt=123a=ssrc-group:FID 33718809 50483271a=ssrc:33718809 cname:ovaCctnHP9Asci9ca=ssrc:33718809 msid:5Y2wZK8nANNAoVw6dSAHVjNxrD1ObBM2kBPV 1d7fc300-9889-4f94-9f35-c0bcc77a260da=ssrc:33718809 mslabel:5Y2wZK8nANNAoVw6dSAHVjNxrD1ObBM2kBPVa=ssrc:33718809 label:1d7fc300-9889-4f94-9f35-c0bcc77a260da=ssrc:50483271 cname:ovaCctnHP9Asci9ca=ssrc:50483271 msid:5Y2wZK8nANNAoVw6dSAHVjNxrD1ObBM2kBPV 1d7fc300-9889-4f94-9f35-c0bcc77a260da=ssrc:50483271 mslabel:5Y2wZK8nANNAoVw6dSAHVjNxrD1ObBM2kBPVa=ssrc:50483271 label:1d7fc300-9889-4f94-9f35-c0bcc77a260d

点击全文阅读


本文链接:http://zhangshiyu.com/post/175518.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1