webrtc学习记录三【创建基于RTCPeerConnection本机内的1v1音视频互通】

系列文章目录

webrtc学习记录一【媒体录制MediaRecorder】

webrtc学习记录二【基于socket.io创建信令服务器聊天室】


目录

系列文章目录

前言

一、媒体能力的协商过程

1.1、RTCpeerConnection类的使用

基本格式

参数说明

configuration详细说明

iceServers详细说明

常见配置示例:

1.2、RTCpeerConnection媒体协商的实例方法

createOffer

基本格式

示例

createAnswer

 基本格式

示例

setLocalDescription

  基本格式

示例

setRemoteDescription

   基本格式

示例

addTrack

 基本格式

参数说明

示例:

removeTrack

 基本格式

addIceCandidate

基本格式

1.3、RTCpeerConnection媒体协商的实例的事件。

onnegotiationneeded

onicecandidate

ontrack

二、端对端连接的基本流程

三、实战,创建一套本机内的1:1音视频互通。

页面基础结构

采集本地数据流

连接远端

创建peerConnection

添加媒体流到peerConnection

本地端开始创建offer

处理offer信息

处理answer信息

处理onicecandidate接收事件

添加远端流


前言

记录webrtc学习过程中的要点,以便温故知新。本章主要是基于RTCPeerConnection,创建一套本机内的1v1音视频系统,没有通过UDP服务器和信令服务器的中转。


提示:以下是本篇文章正文内容,下面案例可供参考

一、媒体能力的协商过程

上图模拟了A和B进行互通时的媒体协商过程,

A需要创建一个offer信息,然后将offer信息通过setLocalDescription设置给peerConnection A;同时,发送offer到信令服务器。

信令服务器接收到A发送过来的offer之后,转发给B,B将来自A的offer信息通过setRemoteDescription设置到远端连接(因为对于B来说,A发送来的信息就是远端)

然后B创建一个answer,然后将answer信息通过setLocalDescription设置给peerConnection B;同时,发送answer信息到信令服务器。

信令服务器接收到B发送过来的answer之后,转发给A,A将来自B的answer信息通过setRemoteDescription设置到远端连接(因为对于A来说,B发送来的信息就是远端)

这样A和B的peerConnection就互相拿到了对方的sdp信息,从而能够实现互通。

1.1、RTCpeerConnection类的使用

上述流程的实现都是基于类RTCpeerConnection,因此下面就开始介绍一下它的用法和相关api

基本格式

var myPeerConnection = new RTCPeerConnection([configuration])

参数说明

参数说明                                                                 
configuration一个对象集合,包含许多配置项。一般主要配置ice服务器地址点对点连接。

configuration详细说明

configuration的详细参数包含如下:

参数说明                                                                 
iceServers最关键的一项,设置STUN 或 TURN服务的地址。不填则限制为本地
iceTransportPolicy

指定ICE传输策略:

relay:只是用中继候选者

all:可以使用任何类型的候选者,包含中继,host类型。但是国内网络基本只有中继能连通、

bundlePolicy

'balanced',音轨与视轨分别使用音频和适配的通道,如2个音轨2个视轨,只要2个通道。

'max-compat',每个轨使用自己的传输通道,如3个视轨需要3个通道

'max-bundle',将音轨和视轨都绑定到同一个传输通道,只使用1个通道

rtcpMuxpolicy

'negotiate',收集RTCP与复用RTP复用的ICE候选者,如果RTCP能复用就与RTP复用,如果不能复用,就将他们单独使用。

'require',只能收集RTCP与RTP复用的ICE候选者,如果RTCP不能复用,则失败。

默认是'require'

peerIdentity建立对等连接时的标识字符串,可以不设置,默认是null
certificates

授权可以使用连接的一组证书,不设置会默认提供一个。

每一个可连通的候选者都需要一个证书,复用情况下有1个即可。

iceCandidatePoolSize

16位的整数值,用于指定预取的ICE候选者的个数。

默认值为 0(意味着不会发生候选预取)

改变 ICE 候选池的大小可能会触发 ICE 收集的开始。

iceServers详细说明

数组类型,可以设置多个STUN或TURN服务地址,每一个都是一个ICE代理的服务。

属性含义
credential凭据,只有TURN服务使用
credentialType凭据类型,可以是password或者oauth
urls用于连接服务中的url数组
username用户名,只有TURN服务使用

常见配置示例:

const peerConnectionConfig = {
    'iceServers': [
        {
            'urls': 'stun:stun.l.google.com:19302',
            'credential': 'mypasswd',
            'username': 'myusername'
        }
    ]
}

const myPeerConnection = new RTCPeerConnection(peerConnectionConfig )

1.2、RTCpeerConnection媒体协商的实例方法

以上面创建的myPeerConnection 实例为例,下面介绍一些必须的示例方法。

createOffer

基本格式

aPromise = myPeerConnection.createOffer([offerOptions])

offerOptions

属性含义

offerToRecieveAudio

0或者1,0表示不传输音频,1表示传输音频

offerToRecieveVideo

0或者1,0表示不传输视频,1表示传输视频

voiceActivityDetection

true/false,默认true,

是否开启静音检测,默认开启。不说话时过滤背景音。

iceRestart

true/false,

该选项会重启ICE,重新进行Candidate收集。可以查看sdp中的ufrag信息判断,对于同一个prconnection,false的时候,每次createOfferufrag不变,true则每次都变化。

缺点是消耗带宽,优点是反馈及时,网络变化时也能确保联通。

示例

myPeerConnection.createOffer({
    offerToRecieveAudio: 0,
    offerToRecieveVideo: 1,
    }).then(desc => {
    }).catch(err => console.log('本地offer创建失败', err))

createAnswer

 基本格式

aPromise = myPeerConnection.createAnswer([answerOptions])

示例

this.pcRemote.createAnswer().then(desc => {
    
}).catch(err => console.log('远端answer创建失败', err))

setLocalDescription

方法中的sessionDescription取的就是offer和answer中返回的desc

  基本格式

aPromise = myPeerConnection.setLocalDescription(sessionDescription)

示例

this.pcLocal.createOffer(offerOptions).then(desc => {
    this.pcLocal.setLocalDescription(desc)
}).catch(err => console.log('本地offer创建失败', err))

setRemoteDescription

   基本格式

aPromise = myPeerConnection.setRemoteDescription(sessionDescription)

示例

this.pcRemote.createAnswer().then(desc => {
    this.pcRemote.setRemoteDescription(desc)
}).catch(err => console.log('远端answer创建失败', err))

addTrack

 基本格式

rtpSender = myPeerConnection.addTrack(track,stream...)

参数说明

属性说明
track添加到RTCPeerConnerction中的媒体轨
sdpMid指定track所在的stream

示例:

this.localStream.getTracks().forEach(track => {
    this.pcLocal.addTrack(track, this.localStream)
})

这里的localStream来自navigator.mediaDevices.getUserMedia,第一章的学习记录中有用到。

removeTrack

对应addTrack,移除轨道。

 基本格式

myPeerConnection.removeTrack(rtpSender)

addIceCandidate

基本格式

aPromise = myPeerConnection.addIceCandidate(candidate)

candidate属性

属性说明
candidate候选者描述信息
sdpMid与候选者相关的媒体流的识别标签
sdpMLineIndex在SDP中m=的索引值
usernameFragment包括了远端的唯一标识

1.3、RTCpeerConnection媒体协商的实例的事件。

onnegotiationneeded

进行媒体协商的时候触发的事件。

onicecandidate

当收到ice候选者的时候触发。

ontrack

媒体轨道添加的时候触发。

二、端对端连接的基本流程

了解了上述api之后,我们再详细的分解一下端对端连接的基本流程,为后续开发做好准备。

  1. 首先是A和B分别连接信令服务器signal,为后续通讯做准备。
  2. 然后A创建peerConnectionA连接,同时添加媒体流到本地视频窗口,以便显示本地视频内容。
  3. A的peerConnectionA连接创建完成后,开始创建offer,然后将offer信息设置到A的peerConnectionA中的setLocalDescription,并且A会对stun/turn服务器发起请求,期望stun/turn服务器返回A解析后的的候选者信息candidateA,同时将offer SDP信息发送给信令服务器。
  4. 信令服务器收到A的offer SDP信息后,马上通过信令服务器传给B。这时B需要先创建B的peerConnectionB,然后将前面信令服务器传过来的offer SDP通过setRemoteDescription设置到远端描述中(因为对于B来说,A传过来的信息就是远端信息)。接下来B开始创建answer,然后将B创建的answer SDP信息通过setLocalDescription设置到peerConnectionB的本地描述信息中。然后将B的candidate信息发送给stun/turn服务器上。同时将answer SDP信息发送给信令服务器。
  5. 信令服务器将answer SDP信息返回给A,A收到后将answer SDP信息通过setRemoteDescription设置到A的远端描述中。
  6. 当A收到了来自ICE服务器中返回的peerConnectionA的候选者信息candidateA后,将candidateA发送给信令服务器。
  7. 信令服务器接收到candidateA后,发送给peerConnectionB,peerConnectionB通过addIceCandidate方法,将candidateA设置给peerConnectionB
  8. 当B收到了来自ICE服务器中返回的peerConnectionB的候选者信息candidateB后,将candidateB发送给信令服务器。
  9. 信令服务器接收到candidateB后,发送给peerConnectionA,peerConnectionA通过addIceCandidate方法,将candidateB设置给peerConnectionA
  10. 至此,peerConnectionA和peerConnectionB就分别获取到了对方的candidate候选者信息,peerConnection的底层就会通过排序,链接检测,找到一个A和B之间的最优通信线路,这样就建立好了一个A和B之间的P2P通道。
  11. 最后通过ontrack事件,监听接收到的A的媒体流,展示到远端。这样A和B之间的p2p音视频互通就基本实现了。

三、实战,创建一套本机内的1:1音视频互通。

由于是 本机内互通,所以信令服务器和stun服务器中转的步骤全部可以忽略。

new RTCPeerConnection([configuration])中的configuration为空时,默认采用本机host来解析,所以我们可以直接拿到candidate

因此上述流程图修改成这样,蓝色线表示修改后的,可能有点乱,大家将就着看一下吧。。。o(╯□╰)o

看下最终需要实现的效果图:

页面基础结构

先写好dom结构

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>peerconnection点对点音视频互通(不含信令服务器)</title>
    <style>
      .btn_wrap{
        display: flex;
      }
      .btn{
        margin-right: 15px;
      }
      .video_wrap{
        display: flex;
      }
      .col{
        margin-right: 20px;
        display: flex;
        flex-direction: column;
      }
      .sdp{
        width: 200px;
      }

    </style>
	</head>
	<body>
		<div id="app">
      <div class="content">
        <div class="btn_wrap">
          <button class="btn" @click="startCollect">开始采集数据,只看的到本地</button>
          <button class="btn" @click="call">呼叫,能看到远端</button>
          <button class="btn" @click="hangup">挂断</button>
        </div>

        <div class="video_wrap">
          <div class="left col">
            <span>本地视频流:</span>
            <video :width="videoSize" :height="videoSize" autoplay ref="localVideo"></video>
            <span>offer SDP</span>
            <textarea class="sdp" readonly :value="offerSDP" cols="30" rows="10"></textarea>
          </div>
          <div class="right col">
            <span>远端视频流:</span>
            <video :width="videoSize" :height="videoSize" autoplay ref="remoteVideo"></video>
            <span>answer SDP</span>
            <textarea class="sdp" readonly :value="answerSDP" cols="30" rows="10"></textarea>
          </div>
        </div>

      </div>
		</div>
	</body>
	<script src="./js/socket.io.js"></script>
	<script src="./js/adapter-latest.js"></script>
	<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
	<script>
		var app = new Vue({
			el: '#app',
			data: {
                answerSDP: '',
                offerSDP: '',
                pcLocal: null,
                pcRemote: null,
                localStream : null,
                constraints: {
                    audio: false,
                    video: true
                },
                videoSize:200,
			},
			methods: {
       
			},
			created() {
				console.log('io', io)
			},
		})
	</script>
<script src="//cdn.jsdelivr.net/npm/eruda"></script>
<script>eruda.init()</script>
</html>

采集本地数据流

先实现简单功能点,点击第一个按钮采集本地数据,这样本地video窗口就能显示。对应学习记录一中的内容。

/**
 * 开始采集,收集本地媒体流信息,并展示。
*/
startCollect() {
  if(!window.navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
    console.log('the getUserMedia is not supported!')
    return false
  } else {
    window.navigator.mediaDevices.getUserMedia(this.constraints).then(stream => {
      this.localStream = stream
      this.$refs.localVideo.srcObject = stream
      console.log('this.$refs.localVideo.srcObject', this.$refs.localVideo.srcObject)
    }).catch(err => {
      console.log('音视频采集错误!', err)
    })
  }
},

连接远端

点击第二个按钮,调用call函数,连接远端。

因为这里我们只模拟本机内的1v1互通,所以不需要信令服务器来中转,也不需要stun/trun服务器解析。

创建peerConnection

先分别创建本地和远端的peerConnection

this.pcLocal = new RTCPeerConnection()
this.pcRemote = new RTCPeerConnection()

添加媒体流到peerConnection

由于媒体流可能有多个,所以这里是一个数组结构,遍历后将每一个媒体轨道都添加上对应的媒体流。

this.localStream.getTracks().forEach(track => {
  this.pcLocal.addTrack(track, this.localStream)
})

本地端开始创建offer

createOffer返回的是一个promise,所以我们直接用then操作来处理后续获得offer之后的处理。

const offerOptions = {
  offerToRecieveAudio: 0,
  offerToRecieveVideo: 1,
}

this.pcLocal.createOffer(offerOptions).then(getOffer).catch(err => console.log('本地offer创建失败', err))

处理offer信息

在前面的步骤拿到offer信息后,马上开始设置描述信息,发送sdp,创建answer

对应流程图中的这一步

const getOffer = desc => {
  this.pcLocal.setLocalDescription(desc)
  this.offerSDP = desc.sdp
  // send desc to signal
  // pcRemote receive desc from signal
  this.pcRemote.setRemoteDescription(desc)
  this.pcRemote.createAnswer().then(getanswer).catch(err => console.log('远端answer创建失败', err))
}

处理answer信息

const getanswer = desc => {
  this.pcRemote.setLocalDescription(desc)
  this.answerSDP = desc.sdp
  // send desc to signal
  // pcLocal receive desc from signal
  this.pcLocal.setRemoteDescription(desc)
  console.log('getanswer')
}

处理onicecandidate接收事件

虽然没有stun/trun服务器,但是RTCPeerConnection会默认采集本机host信息来解析candidate,所以依然会有onicecandidate事件。

// 这几个事件是异步回调。
/**
 * 
*/
this.pcLocal.onicecandidate = e => {
  // pcLocal send candidate to signal
  // pcRemote revieve candidate from signal
  console.log('pcLocal.onicecandidate')
  this.pcRemote.addIceCandidate(e.candidate)
}
this.pcRemote.onicecandidate = e => {
  console.log('pcRemote.onicecandidate')
  this.pcLocal.addIceCandidate(e.candidate)
}

添加远端流

最后,通过ontrack事件监听媒体轨是否添加成功,添加成功后将媒体流信息输出到界面上即可。

this.pcRemote.ontrack = e => {
  console.log('pcRemote.onTrack')
  this.$refs.remoteVideo.srcObject = e.streams[0]
}

完整代码见git地址:

https://github.com/Silent-Jude/webRtcLearning/blob/main/public/peerconnection_local/index.html

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值