webrtc 入门第三章 建立连接

这篇具有很好参考价值的文章主要介绍了webrtc 入门第三章 建立连接。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

webrtc 入门第三章 建立连接

一、介绍
1、概述

​ 在前面的章节我们学习了通过webrtc的基本操作实现了获取本地媒体流、音视频的获取与操作。在得到本地的媒体流后我们就需要将本地媒体数据发送到远端,远端街道媒体流后渲染可视化,从而达到通话的目的。

​ RTCPeerConnection 连接的核心pai接口,使用它可以将本地流发送到远端,同时也可以将远端媒体流发送到本地从而实现连接。在使用过程中需要用到信令服务器中转信息和STUN服务器打桩服务。

二、实践
1、RTCPeerConnection 连接对象

1.RTCPeerConnection 后文简称pc连接对象。本地为Local对象,远端为Remote对象,在一对一音视频通话场景中pc对象总是成对出现。

方法名 参数 说明
RTCPeerConnection RTCConfiguration连接配置参数 RTCPeerConnection接口代表一个本地到远端的webrtc连接,这个连接提供了创建,保持,监控,关闭连接的方法实现,在创建时需要向其传入配置参数,及ICE配置信息
pc.createOffer RTCOfferOptions对象 创建提议Offer方法,此方法会返回SDP offer信息,即RTCSessionDescription对象
pc.setLocalDescription RTCSessionDescription 对象 设置本地SDP描述信息
pc.setRemoteDescription RTCSessionDescription 对象 设置远端SDP描述信息,接收到远端发来的SDP信息后使用本方法
pc.createAnswer RTCAnswerOptions 对象,可选 创建应答Answer方法,此方法会返回SDPAnswer信息,即RTCSessionDescription 对象
RTCPIceCandidate wevrtc网络信息,端口,ip等。
pc.addIceCandidate RTCPIceCandidate对象 pc连接添加对方法的IceCandidate对象,得到对放的网络地址等
2.流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u3UNWW3f-1646320648144)(C:\Users\Administrator\Desktop\liucheng.png)]

3、连接流程

WebRTC连接流程比较复杂,学习过程只考虑将本地Peer-A的流发送到远端Peer-B,具体流程如下(A-本地,B-远端)

1.A获取到本地媒体流 MediaStram:通过getUserMedia 方法获取到本地的音视频流数据

2.A生成本地连接对象PC-A:创建一个RTCPeerConnection接口,该接口提供创建、保持、关闭等方法,在设置前需要设置ICE服务器地址

varconfiguration={"iceServers": [{"url": "stun:stun.1.google.com:19302"}]},
that.peerConnA = new RTCPeerConnection(that.configuration)

3.A将本地视频流加入PC-A :

that.localStream.getTracks().forEach((track) => {
    that.peerConnA.addTrack(track, that.localStream)
})

4.A创建提议Offer: that.peerConnA.createOffer() 返回一个RTCPSessionDescription对象,主要是SDP信息是会话的描述信息,两个端连接过程中通过他来进行互相的信息交换,达到媒体协商。

5.A-设置本地描述:创建offer成功后设置本地的描述信息 that.peerConnA.setLocalDescription(event)

6.A将Offer发送给B:通常需要一个信令服务器例如websocket 来转发offer数据

7.B生成PC-B对象:同A端一样,B端也要生成一个RCTPeerConnection对象来应答A端发送的Answer,媒体流等

8.B端设置远端描述:当B端接收到来自A端的offer信息后使用setRemoteDescription() 方法设置来自远端A的描述信息

9.B端生成应答Answer信息:B端使用pc.ceateAnswer()方法生成一个应答A端RTCPSessionDescription对象,主要包括SDP信息,应答Answer和提议Offer是成对出现的。

10.B端设置本地描述:B端创建Answer后设置本地的描述信息 that.peerConnB.setLocalDescription(event)

11.B端返回Answer给A端:通过信令服务器将Answer发送给A端

12.A端设置来自B端的Answer描述信息:当A端通过websocket信令服务获得到的Answer信息后,调用that.peerConnA.setRemoteDescription()方法设置远端的描述

13.交换ICE候选地址信息:建立连接时,会回调onicecandidate事件,传递ice候选地址,同样也需要websocket信令服务来做消息转发,对方接受到以后调用addIceCandidate()方法设置接收到的候选地址

14.交换与使用媒体流:当一方执行addTrack后,另一方的RTCPerrConnection会触发track事件回调,在回调事件中可以获得对方的轨道里的媒体流,这样就能播放对方的流媒体。

that.peerConnB.addEventListener("track", that.getRemoteStream)
getRemoteStream: function (event) {
  let that = this
      if (that.remoteVideo.srcObject !== event.streams[0]) {
             that.remoteVideo.srcObject = event.streams[0]
             this.remoteVideo.play();
             console.log("开始获取远端视频流")
      }
}
4、示例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>RTCPeerConnection 连接测试</title>
</head>
<body>
<div class="app">
    <input type="button" title="开始" value="开始" v-on:click="start"/>
    <input type="button" title="呼叫" value="呼叫" v-on:click="call"/>
    <input type="button" title="挂断" value="挂断" v-on:click="stop"/>
    <input type="button" value="同步" v-on:click="canPlay"/>
    <hr>

    <span>当前使用视频设备:{[videoDeviceName]}</span>
    <br> <br>
    <span>当前使用音频设备:{[audioDeviceName]}</span>
    <hr>

    {{/*    本地视频*/}}
    <video id="localVideo" class="localVideo" height="240px" width="280px" playsinline autoplay muted></video>
    {{/*    远端视频*/}}
    <video id="remoteVideo" class="remoteVideo" height="240px" width="280px" playsinline autoplay muted></video>
    {{/*    本地mp4文件*/}}

    <br> <br>
    <hr>
    <span>测试本地mp4文件</span>

    <br>

    <br>
    <video id="myVideo" class="myVideo" autoplay="autoplay" width="400" height="200" controls loop muted
           v-on:oncanplay="canPlay">
        <source src="/static/file/capture.mp4" type="video/mp4">
    </video>

</div>
</body>
<script src="/static/js/Vue.2.5.3.js"></script>
<script type="text/javascript">
    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
    let vm = new Vue({
        el: ".app",
        delimiters: ['{[', ']}'],
        data: {
            // 测试我的视频
            myVideo: null,
            // 本地视频
            localVideo: null,
            // 本地视频流
            localStream: null,
            // 远端视频流
            remoteVideo: null,
            isOpen: false,
            videoDeviceName: "",
            audioDeviceName: "",
            // ICE service地址
            configuration: {
                "iceServers": [{"url": "stun:stun.1.google.com:19302"}]
            },
            // peerConnA 本地对象
            peerConnA: null,
            // peerConnB 远程对象
            peerConnB: null,
            // 本地视频轨道
            videoTracks: [],
            // 本地音频轨道
            audioTrack: []
        },
        methods: {
            canPlay: function () {
                alert("zheli")
                let fps = 0;
                // 捕捉
                if (this.myVideo.captureStream) {
                    this.localStream = this.myVideo.captureStream(fps)
                } else if (this.capture.mozCaptureStream) {
                    this.localStream = this.myVideo.mozCaptureStream(fps)
                } else {
                    alert("不支持 captureStream")
                }

            },
            stop: function () {
                let that = this
                that.peerConnB.close()
                that.peerConnA.close()
                that.peerConnA = null
                that.peerConnB = null
                console.log("关闭会话")
            }
            ,
            start: async function () {
                let that = this;
                if (that.isOpen) {
                    return
                }
                try {
                    that.localStream = await navigator.mediaDevices.getUserMedia({audio: true, video: true})
                    that.isOpen = true
                    that.localVideo.srcObject = that.localStream
                    that.remoteVideo.srcObject = that.localStream
                    console.log("获取本地流成功", that.localStream)
                    // 获取设备轨道
                    that.videoTracks = that.localStream.getVideoTracks()
                    that.audioTrack = that.localStream.getAudioTracks()
                    if (that.videoTracks.length > 0) {
                        that.videoDeviceName = that.videoTracks[0].label
                    }
                    if (that.audioTrack.length > 0) {
                        that.audioDeviceName = that.audioTrack[0].label
                    }

                } catch (e) {
                    console.log("getUserMedia 错误" + e)
                }
            }
            ,
            call: async function () {
                let that = this;
                console.log("开始呼叫")

                //  监听返回icecandidate 信息
                that.peerConnA = new RTCPeerConnection(that.configuration)
                that.peerConnA.addEventListener("icecandidate", that.onIceCandidateA)
                that.peerConnB = new RTCPeerConnection(that.configuration)
                that.peerConnB.addEventListener("icecandidate", that.onIceCandidateB)
                // 监听ICE状态变化
                that.peerConnA.addEventListener("iceconnectionstatechange", that.onIceStateChangeA)
                that.peerConnB.addEventListener("iceconnectionstatechange", that.onIceStateChangeB)

                // 监听track,获取远端视频流视频
                that.peerConnB.addEventListener("track", that.getRemoteStream)
                // 将本地流加入本地连接
                that.localStream.getTracks().forEach((track) => {
                    that.peerConnA.addTrack(track, that.localStream)
                })


                // 创建通话offer
                try {
                    console.log("peerConnA 创建offer会话开始")
                    const offer = await that.peerConnA.createOffer()
                    await that.onCreateOfferSuccess(offer)
                } catch (e) {
                    console.log("创建会话描述SD失败:", e.toString())
                }
            }
            ,

            // 创建提议offer成功
            onCreateOfferSuccess: async function (event) {
                let that = this
                // 设置连接描述
                console.log("peerConnA 创建offer返回得SDP信息", event.sdp)
                console.log("设置peerConnA得本地描述start...")
                try {
                    await that.peerConnA.setLocalDescription(event)
                    console.log("设置peerConnA得本地描述成功")
                } catch (e) {
                    console.log("设置peerConnA得本地描述错误:", e.toString())
                }

                console.log("设置peerConnB得远端描述 start")
                try {
                    await that.peerConnB.setRemoteDescription(event)
                    console.log("设置peerConnB得远端描述成功")

                } catch (e) {
                    console.log("设置peerConnB得远端描述错误:", e.toString())
                }

                // 开始应答
                console.log("peerConnB创建应答 answer start")
                try {
                    const answer = await that.peerConnB.createAnswer()
                    console.log("peerConnB创建应答成功")
                    await that.onCreateAnswerSuccess(answer)
                } catch (e) {
                    console.log("peerConnB创建应答错误:", e.toString())
                }

            }
            ,

            // 创建answer应答成功
            onCreateAnswerSuccess: async function (answer) {
                let that = this
                console.log("peerConnB创建应答answer数据:", answer)
                console.log("peerConnA与peerConnB交换应答answer信息 start")

                try {
                    await that.peerConnB.setLocalDescription(answer)
                    console.log("设置peerConnB得本地answer 应答远端描述成功")

                } catch (e) {
                    console.log("设置peerConnB得本地answer应答描述错误:", e.toString())
                }

                try {
                    await that.peerConnA.setRemoteDescription(answer)
                    console.log("设置peerConnA得远端answer应答描述成功")

                } catch (e) {
                    console.log("设置peerConnA得远端answer应答描述错误:", e.toString())
                }
            }
            ,

            // 获取远端视频
            getRemoteStream: function (event) {
                let that = this
                console.log("获取远端视频数据如下:")
                console.log(event)
                if (that.remoteVideo.srcObject !== event.streams[0]) {
                    that.remoteVideo.srcObject = event.streams[0]
                    this.remoteVideo.play();
                    console.log("开始获取远端视频流")
                }
            }
            ,
            // 监听ICE状态变化事件回调方法
            onIceStateChangeA: function (event) {
                console.log("监听 peerConnA ICE状态", this.peerConnA.iceConnectionState)
                console.log(event)
            }
            ,
            // 监听ICE状态变化事件回调方法
            onIceStateChangeB: async function (event) {
                console.log("监听 peerConnB ICE状态", this.peerConnB.iceConnectionState)
                console.log(event)
            }
            ,

            onIceCandidateA: async function (event) {
                let that = this

                try {
                    if (event.candidate) {
                        // 直接交换candidate数据,就不需要通过信令服务器传送
                        await that.peerConnB.addIceCandidate(event.candidate)
                        console.log("peerConnB IceCandidate----------")
                        console.log(event)
                        that.onAddIceCandidateSuccess(that.peerConnB)
                    }
                } catch (e) {
                    that.onAddIceCandidateError(that.peerConnB, e)
                }
                console.log("onIceCandidateA data:" + event.candidate)
            }
            ,
            onIceCandidateB: async function (event) {
                let that = this
                try {
                    if (event.candidate) {
                        await that.peerConnA.addIceCandidate(event.candidate)
                        console.log("peerConnA IceCandidate----------")
                        console.log(event)
                        that.onAddIceCandidateSuccess(that.peerConnA)
                    }
                } catch (e) {
                    that.onAddIceCandidateError(that.peerConnA, e)
                }
                console.log("onIceCandidateB data:" + event.candidate)
            },
            //
            onAddIceCandidateSuccess: function (pc) {
                console.log("添加" + this.getPcName(pc) + "      IceCandidate 成功")
            },

            onAddIceCandidateError: function (pc, err) {
                console.log("添加" + this.getPcName(pc) + "       IceCandidate 失败" + err.toString())
            },
            getPcName: function (pc) {
                return (pc === this.peerConnA) ? "peerConnA" : "peerConnB"
            },
        }
        ,
        mounted: function () {
            this.localVideo = document.getElementById('localVideo');
            this.remoteVideo = document.getElementById('remoteVideo');
            this.myVideo = document.getElementById('myVideo');
        }
    })
</script>
</html>

webrtc 入门第三章 建立连接

			本地								远端同步           

在上述程序中未使用信令服务器作交换ICE和SDP数据,而是采用本地直接添加的方式,来实现了RTCPeerConnection的连接流程。

其连接过程也和第三段的流程一样,先获取本地的媒体地址,然后发起协商offer,设置本地描述,收到远端的协商offer后设置远端描述,生成会话offer,设置本地描述后发给提议方,提议方收到应答会话offer后,设置远端描述,整个流程结束。

三、总结

本章介绍了webrtc的连接流程即api,在一对一的对接过程中可以直接使用,其连接过程比较复杂也相当繁琐。学者需要先了解其连接原理和流程,然后再去结合代码即可掌握。另外学者们需要掌握一下几点。

1.对于媒体流的操作转换例如:获取视频的尺寸格式,监听远端视频流的变化,音频大小变化,视频清晰度自适应,编码方式。

2.了解提议(offer)/应答(answer)里的信息:这些是SDP信息包含,如分辨率,格式,编码等。

3.了解Candidate信息:这些也是SDP信息,里面包括媒体协商的信息,主要包括服务信息,如中继,打桩,服务器的ip和端口

地址,然后发起协商offer,设置本地描述,收到远端的协商offer后设置远端描述,生成会话offer,设置本地描述后发给提议方,提议方收到应答会话offer后,设置远端描述,整个流程结束。

三、总结

本章介绍了webrtc的连接流程即api,在一对一的对接过程中可以直接使用,其连接过程比较复杂也相当繁琐。学者需要先了解其连接原理和流程,然后再去结合代码即可掌握。另外学者们需要掌握一下几点。

1.对于媒体流的操作转换例如:获取视频的尺寸格式,监听远端视频流的变化,音频大小变化,视频清晰度自适应,编码方式。

2.了解提议(offer)/应答(answer)里的信息:这些是SDP信息包含,如分辨率,格式,编码等。

3.了解Candidate信息:这些也是SDP信息,里面包括媒体协商的信息,主要包括服务信息,如中继,打桩,服务器的ip和端口

4.通过学习视频连接后,可以进行举一反三实现canvas绘画板的同步功能。文章来源地址https://www.toymoban.com/news/detail-409610.html

到了这里,关于webrtc 入门第三章 建立连接的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【UnityShader入门精要学习笔记】第三章(1)Unity Shader介绍

    本系列为作者学习UnityShader入门精要而作的笔记,内容将包括: 书本中句子照抄 + 个人批注 项目源码 一堆新手会犯的错误 潜在的太监断更,有始无终 总之适用于同样开始学习Shader的同学们进行有取舍的参考。 从本章节开始我们要学习Shader相关的知识了,诸位看客可能有的

    2024年02月02日
    浏览(71)
  • 《Jetpack Compose从入门到实战》第三章 定制 UI 视图

    -ui.theme.Color.kt ui.theme.Type.kt 先将Nunito Sans字体家族放入 res/font,再根据设计稿写代码 ui.theme/Shape.kt CompositionLocal 是 Jetpack Compose 中的一种数据传递方式。它可以在组合组件之间传递可变数据,而无需通过 props 或 state 管理器来传递数据。这个特性比传统的数据传递方式更为高效

    2024年02月07日
    浏览(48)
  • 【UnityShader入门精要学习笔记】第三章(2)Unity Shader的形式,章节答疑

    本系列为作者学习UnityShader入门精要而作的笔记,内容将包括: 书本中句子照抄 + 个人批注 项目源码 一堆新手会犯的错误 潜在的太监断更,有始无终 总之适用于同样开始学习Shader的同学们进行有取舍的参考。 (该系列笔记中大多数都会复习前文的知识,特别是前文知识非

    2024年02月02日
    浏览(52)
  • 「第三章」python-docx 添加标题,word标题从入门到精通

    💡 1. add_heading() 简介 💡 2. add_heading() 基本用法 💡 3. 设置不同级别的标题 💡 4. 设置带有特殊字符的标题 💡 5. 使用循环添加多个标题 💡 6. 使用不同样式添加标题 💡 7. 结合其他元素使用标题 💡 8. 为标题设置复杂多变的样式 最近一段时间,一直在更新python关于PDF文档、

    2024年02月02日
    浏览(43)
  • maven从入门到精通 第三章 Maven中形成web对Java工程的依赖

    从来只有war包中含有jar包,而没有jar包中含有war包 web工程依赖的java工程,就是jar包,这个jar包经过自动化部署后,会放在web工程的web-inf/lib目录下 在 pro02-maven-web 工程的 pom.xml 中,找到 dependencies 标签,在 dependencies 标签中做如下配置 证明在Web工程中可以使用Java工程中创建的

    2024年02月02日
    浏览(48)
  • 【第三章 Python 机器学习入门之Series和DataFrame的创建、索引、切片、数据清洗、数据分析等】

    第一章 Python 机器学习入门之Pandas库的使用 第二章 Python 机器学习入门之NumPy库的使用 第四章 Python 机器学习入门之数据可视化 第五章 Python 机器学习入门之机器学习算法 第六章 Python 机器学习入门之实战项目 Series是一种一维数组,可以通过以下方式创建: 通过列表创建Ser

    2024年02月05日
    浏览(60)
  • Linux第三章

    无论是Windows、MacOS、Linux均采用多用户的管理模式进行权限管理。在Linux系统中,拥有最大权限的账户名为:root(超级管理员) root用户拥有最大的系统操作权限,而普通用户在许多地方的权限是受限的(普通用户的权限,一般在其HOME目录内是不受限的,一旦出了HOME目录,大

    2023年04月26日
    浏览(54)
  • 第三章 decimal模块

    decimal 模块是 Python 提供的用于进行十进制定点和浮点运算的内置模块。使用它可以快速正确地进行十进制定点和浮点数的舍入运算,并且可以控制有效数字的个数。 使用 decimal 模块主要是因为它与 Python 自带的浮点数相比,有以下优点 : 基于浮点模型,提供与数字计算相同

    2024年02月09日
    浏览(58)
  • 第三章-运输层

    运输层协议为运行在不同主机上的进程之间提供逻辑通信,即从应用程序角度看两个主机好像直连一样,实际可能相隔万里 运输层协议是在端系统上实现的,而不是路由器,为什么这么强调,因为运输层会将应用报文划分为较小的块然后加上一个运输层首部来生成运输层报文

    2024年02月14日
    浏览(41)
  • 第三章 选择与循环

    程序员必备技能(思想):增量编写法。每写一部分代码要及时运行看结果是否正确,对于复杂程序很重要。 常用的运算符优先级: 逻辑非 ! 算术运算符 关系运算符 || 赋值运算符 单目运算符 逻辑非 ! 算术运算符 +、-、×、/、% 关系运算符 、、=、=、==、!= 逻辑运算符 、|

    2024年02月09日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包