WebRTC 笔记

news/2025/1/20 9:24:43/文章来源:https://www.cnblogs.com/TMesh/p/18680753

目录
  • 通话建立流程
    • 特别提醒
  • 代码
    • 创建 RTCPeerConnection 对象
    • 获取本地摄像头/麦克风
    • 创建发起方会话描述对象(createOffer)
    • 连接的远程对等方属性(setRemoteDescription)
    • 建立一条最优的连接方式

WebRTC 允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流和(或)音频流或者其他任意数据的传输
本示例主要使用了 WebRTC 和 WebSocket

  • WebRTC(Web Real-Time Communication)即网页即时通信,是一个支持网页浏览器进行实时语音对话或视频对话的 API。
  • WebSocket是一种在单个 TCP 连接上进行全双工通信的协议。在 WebSocket 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

通话建立流程

简单说一下流程,如 Client A 想和 Client B 进行音视频通话:

  1. Client A、Client B 都连接信令服务器(ws);
  2. Client A 创建本地视频,并获取会话描述对象(offer sdp)信息,Client A 创建端点(Create PeerConnection),并添加音视频流(Add Streams);
  3. Client A 将 offer sdp 通过 ws 发送给 Client B;
  4. Client B 收到信令后,Client B 创建本地视频,并获取会话描述对象(answer sdp)信息,Client B 创建端点(Create PeerConnection),并添加音视频流(Add Streams);
  5. Client B 将 answer sdp 通过 ws 发送给 Client A;
  6. Client A 收到用于 answer 的 SDP 后,保存下来;
  7. Client A 创建一个用于 offer 的 SDP 对象(Create Office),保存并发送给 Client B;
  8. Client B 保存收到的用 于offer 的 SDP 对象。
  9. Client A 和 Client B 开始打洞,收集并通过 ws 交换 ice 信息;
  10. 完成打洞后,Client A 和 Client B 开始为安全的媒体通信协商秘钥;
  11. 至此, Client A 和 Client B 可以进行音视频通话。
    引用网上的有关 WebRTC 建立的时序图,可能更加直观:
    [Pasted image 20250114164552.png]
    [6042199ed2c298dc5b0717130687e2b5.jpeg]
    从上述流程,可以发现通信双方在建立连接前需要交换信息
    WebRTC 并不提供 Stun 服务器Signal 服务器,服务器端需要自己实现。Stun 服务器可以用 google 提供的实现 stun 协议的测试服务器(stun:stun.l.google.com:19302),Signal 服务器则完全需要自己实现了,用于转发信息,它需要在 ClientA 和 ClientB 之间传送彼此的 SDP 信息和 candidate 信息,ClientA 和 ClientB 通过这些信息建立 P2P 连接来传送音视频数据。由于网络环境的复杂性,并不是所有的客户端之间都能够建立 P2P 连接,这种情况下就需要有个 relay 服务器做音视频数据的中转。
    Signal 服务器其实就是我们的 websocketstun 服务器 其实就是 webrtc 啦。
    WebRTC 不借助中间媒介 的意思是,在建立对等连接后,不需要借助第三方服务器中转,而是直接在两个实体(浏览器)间进行传输。
    完整的流程:
  • ClientA 首先创建 PeerConnection 对象,然后打开本地音视频设备,将音视频数据封装成 MediaStream 添加到 PeerConnection 中。
  • ClientA 调用 PeerConnection 的 CreateOffer 方法创建一个用于 offer 的 SDP 对象,SDP对象中保存当前音视频的相关参数。ClientA 通过 PeerConnection 的 SetLocalDescription 方法将该 SDP 对象保存起来,并通过 Signal 服务器发送给 ClientB
  • ClientB 接收到 ClientA 发送过的 offer SDP 对象,通过 PeerConnection 的SetRemoteDescription 方法将其保存起来,并调用 PeerConnection 的 CreateAnswer 方法创建一个应答的 SDP 对象,通过 PeerConnection 的 SetLocalDescription 的方法保存该应答 SDP 对象并将它通过 Signal 服务器发送给 ClientA
  • ClientA 接收到 ClientB 发送过来的应答 SDP 对象,将其通过 PeerConnection 的 SetRemoteDescription 方法保存起来。
  • 在 SDP 信息的 offer/answer 流程中,ClientA 和 ClientB 已经根据 SDP 信息创建好相应的音频 Channel 和视频 Channel 并开启 Candidate 数据的收集,Candidate 数据可以简单地理解成 Client 端的 IP 地址信息(本地 IP 地址、公网 IP 地址、Relay 服务端分配的地址)。
  • 当 ClientA 收集到 Candidate 信息后,PeerConnection 会通过 OnIceCandidate 接口给 ClientA 发送通知,ClientA 将收到的 Candidate 信息通过 Signal 服务器发送给 ClientB,ClientB 通过 PeerConnection 的 AddIceCandidate 方法保存起来。同样的操作 ClientB 对 ClientA 再来一次。
  • 这样 ClientA 和 ClientB 就已经建立了音视频传输的 P2P 通道,ClientB 接收到 ClientA 传送过来的音视频流,会通过 PeerConnection 的 OnAddStream 回调接口返回一个标识 ClientA 端音视频流的 MediaStream 对象,在 ClientB 端渲染出来即可。同样操作也适应 ClientB 到 ClientA 的音视频流的传输。

特别提醒

1、创建SDP对象之前,必须先打开本地音视频流
2、打开音视频流之前,必须先绑定事件(bindOnIceCandidate、bindOnTrack)
3、创建用于 answer 的 SDP 对象 之前,必须先保存用于 offer 的 SDP 对象

以上步骤完成之后:

  1. rtc 会自动收集 Candidate 信息,并通过回调函数通知你,用于交换 Candidate 信息。
  2. 交换完 Candidate 信息后,P2P 连接就建立好了。并通过回调函数,将远程视频流给你

代码

创建 RTCPeerConnection 对象

获取视频标签,连接信令服务器,创建 RTCPeerConnection 对象。其中 RTCPeerConnection 的作用是在两个对等端之间建立连接,其构造函数支持传一个配置对象,包含 ICE “打洞”(由于本示例在本机进行测试,故不需要)。

const localVideo = document.querySelector('#local-video');
const remoteVideo = document.querySelector('#remote-video');
const socket = new WebSocket('ws://localhost:8080');
const peer = new RTCPeerConnection();socket.onmessage = () => { // todo }
peer.ontrack = () => { // todo }
peer.onicecandidate = () => { // todo }

获取本地摄像头/麦克风

获取本地摄像头/麦克风(需要允许使用权限),拿到本地媒体流(MediaStream)后,需要将其中所有媒体轨道(MediaStreamTrack)添加到轨道集,这些轨道将被发送到另一对等方。

navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(stream => {localVideo.srcObject = stream;stream.getTracks().forEach(track => {peer.addTrack(track, stream);});});

创建发起方会话描述对象(createOffer)

创建发起方会话描述对象(createOffer),设置本地 SDP(setLocalDescription),并通过信令服务器发送到对等端,以启动与远程对等端的新 WebRTC 连接。

peer.createOffer().then(offer => {peer.setLocalDescription(offer);socket.send(JSON.stringify(offer));
});

当调用 setLocalDescription 方法,PeerConnection 开始收集候选人(ice 信息),并发送 offer_ice 到对等方。这边补充第一步中的 peer.onicecandidate 和 socket.onmessage
对等方收到 ice信息后,通过调用 addIceCandidate 将接收的候选者信息传递给浏览器的 ICE 代理。

peer.onicecandidate = e => {if (e.candidate) {socket.send(JSON.stringify({type: 'offer_ice',iceCandidate: e.candidate}));} 
};socket.onmessage = e => {const { type, sdp, iceCandidate } = JSON.parse(e.data);if (type === 'offer_ice') {peer.addIceCandidate(iceCandidate);}
}

连接的远程对等方属性(setRemoteDescription)

接收方收到了offer信令后,开始获取摄像头/麦克风,与发起方操作一致。同时将收到 offer SDP 指定为连接的远程对等方属性(setRemoteDescription),并创建应答 SDP(createAnswer),发送到对等端。这边补充第一步中的 socket.onmessage。

socket.onmessage = e => {const { type, sdp, iceCandidate } = JSON.parse(e.data);if (type === 'offer') {navigator.mediaDevices.getUserMedia();        // 与发起方一致,省略const offerSdp = new RTCSessionDescription({ type, sdp });peer.setRemoteDescription(offerSdp).then(() => {peer.createAnswer(answer => {socket.send(JSON.stringify(answer));peer.setLocalDescription(answer)});});}
}

注意:当 setLocalDescription 方法调用后,开始收集候选人信息,并发送 answer_ice 到对等方。与发送方同理,不赘述。

建立一条最优的连接方式

通过不断收集 ICE 信息(onicecandidate),发起方和应答方最终将建立一条最优的连接方式,此时会触发 ontrack 回调,即可获取到对等方的媒体流。

peer.ontrack = e => {if (e && e.streams) {remoteVideo.srcObject = e.streams[0];}
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/871999.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

读量子霸权09电池

爱迪生、福特推动工业电气化,电池效率低是能源存储难题,量子计算机或助力电池设计与能源存储,锂空气电池、超级电池是候选技术,量子计算也应用于汽车设计。1. 能量 1.1. 爱迪生1.1.1. 爱迪生是工业和社会电气化背后不知疲倦的推动力1.1.2. 爱迪生青睐于电池1.2. 福特1.2.1.…

优化@Transactional事务性能(LazyConnectionDataSourceProxy)

背景 在项目开发中,使用 @Transactional 注解来管理事务非常方便,且优雅。但是也存在一个问题:长事务问题很多被 @Transactional 标记的方法,实际上并不需要进行数据库操作,或者说,它们在执行的很长一段时间内都不会真正触发数据库访问。举个例子,我们的业务逻辑可能如下…

ElasticSearch Java 使用

目录创建工程,导入坐标创建索引 index创建映射 mapping建立文档 document建立文档(通过 XContentBuilder)建立文档(使用 Jackson 转换实体)1)添加jackson坐标2)创建 Article 实体3)代码实现查询文档操作关键词查询字符串查询使用文档 ID 查询文档查询文档分页操作批量插…

IntelliJ IDEA 2024.3 Java开发工具

IntelliJ IDEA 2024.3 Java开发工具 JetBrains IntelliJ IDEA 2024 mac,是一款Java开发工具,IntelliJ IDEA 凭借无与伦比的 Java 和 Kotlin 支持脱颖而出。从一开始就支持尖IDEA 2024.3 中文版开发工具端语言功能,保持领先地位。IntelliJ IDEA 对您的代码了如指掌,利用这些…

AI应用实战课学习总结(6)分类算法分析实战

本文介绍了机器学习中的分类场景问题,常用的分类算法 以及 分类和回归的简单对比,最后通过一个医疗数据诊断分类的案例做了一次实战,相信对你理解分类应用应该有所帮助。大家好,我是Edison。 最近入坑黄佳老师的《AI应用实战课》,记录下我的学习之旅,也算是总结回顾。 今…

深入浅出索引(上)

1.什么是数据库索引,索引是干什么用的? 对于数据库的表而言,索引其实就是它的“目录”。 2.索引的三种实现方式?(暂时介绍3种) ①哈希表索引:哈希表是一种以键 - 值(key-value)存储数据的结构,我们只要输入待查找的值即 key,就可以找到其对应的值即 Value。哈希的思…

【推荐】一款开源且功能丰富、技术先进的在线项目任务管理系统

项目介绍 DooTask是一款基于PHP + Vue开源的、功能丰富、技术先进、安全性高的开源在线项目任务管理系统,适合各种规模的团队使用。提供各类文档协作工具、在线思维导图、在线流程图、项目管理、任务分发、即时IM,文件管理等工具;同时消息功能使用非对称加密技术让你的沟通更…

《SpringBoot》自动装配原理(简单易懂)

引入 先看SpringBoot的主配置类 @SpringBootApplication public class DemoApplication{public static void main(String[] args){SpringApplication.run(StartEurekaApplication.class, args);} }@SpringBootApplication 点进@SpringBootApplication来看,发现@SpringBootAppl…

VMware Data Services Manager 2.2 发布 - 数据库管理和数据服务管理

VMware Data Services Manager 2.2 发布 - 数据库管理和数据服务管理VMware Data Services Manager 2.2 发布 - 数据库管理和数据服务管理 在 vSphere 环境中按需配置和自动管理 PostgreSQL 和 MySQL 数据库 请访问原文链接:https://sysin.org/blog/vmware-data-services-mana…

PyMuPDF4LLM : 提取 PDF 数据的终结者

你知道处理 PDF 文件时会遇到什么,尤其是那些包含复杂研究内容或者有表格、图像和额外信息的文档。对于 AI 领域的人,特别是那些在调整大型语言模型(LLM)或构建知识检索系统(比如 RAG)的人来说,提取正确的数据非常重要。然而,老实说,并不是所有的 PDF 提取工具都能做到…

【Java开发】LLM实战:使用LangChain4j构建本地RAG系统

一、引言二、基本概念2.1 什么是RAG2.2 LangChain4j简介2.3 大模型开发 vs. 传统JAVA开发三、实战经验3.1 环境搭建3.1.1 向量库(Chroma)  3.1.2 集成LangChain4j3.2 程序编写3.2.1 项目结构3.2.2 知识采集3.2.3 文档切分  3.2.4 文本向量化3.2.5 向量…

3D NAND闪存技术的架构和工艺集成概述(下)

另一种具有FG的3D NAND结构是HC-FG结构,如图8a所示。与DC-SF结构不同,水平信道堆叠在HC-FG架构中;然而,单元格没有被CG包围。因此,FG单元可以通过沟道优先工艺堆叠,类似于传统的2D平面灰阵列,并且可以以低成本实现3D结构。此外,如图8b所示,HC-FG结构与层选择晶体管(L…