WebRTC 初探
前言
项目中有局域网投屏与文件传输的需求,所以研究了一下 webRTC,这里记录一下学习过程。
WebRTC 基本流程以及概念
下面以 1 对 1 音视频实时通话案例介绍 WebRTC 的基本流程以及概念
WebRTC 中的角色
-
WebRTC 终端,负责音视频采集、编解码、NAT 穿越、音视频数据传输
-
Signal 服务器,负责信令处理,如加入房间、离开房间、媒体协商消息的传递等。
-
STUN/TURN 服务器,负责获取 WebRTC 终端在公网的 IP 地址,以及 NAT 穿越失败后的数据中转。
媒体协商
SDP(Session Description Protocal): 用文本描述的各端(PC 端、Mac 端、Android 端、iOS 端等)的能力。
这里的能力指的是各端所支持的音频编解码器是什么,这些编解码器设定的参数是什么,使用的传输协议是什么,以及包括的音视频媒体是什么
媒体协商流程图
-
呼叫方创建 Offer 类型的 SDP 消息。创建完成后,调用 setLocalDescriptoin 方法将该 Offer 保存到本地 Local 域,然后通过信令将 Offer 发送给被呼叫方。
-
被呼叫方收到 Offer 类型的 SDP 消息后,调用 setRemoteDescription 方法将 Offer 保存到它的 Remote 域。作为应答,被呼叫方要创建 Answer 类型的 SDP 消息,
-
Answer 消息创建成功后,再调用 setLocalDescription 方法将 Answer 类型的 SDP 消息保存到本地的 Local 域。最后,被呼叫方将 Answer 消息通过信令发送给呼叫方。
-
呼叫方收到 Answer 类型的消息后,调用 RTCPeerConnecton 对象的 setRemoteDescription 方法,将 Answer 保存到它的 Remote 域
具体的媒体协商的过程是 WebRTC 内部自己去实现的, 作为开发者只需要记住本地的 SDP 和远端的 SDP 都设置好后,协商就算成功了.
紧接着在 WebRTC 底层会收集 Candidate,并进行连通性检测,最终在通话双方之间建立起一条链路来。
WebRTC 的连接 与 ICE Candidate(重点)
WebRTC 之间建立连接的过程是非常复杂的, 主要的原因在于它内部的实现既要考虑传输的高效性,又要保证端与端之间的连通率。
当同时存在多个有效连接时,它首先选择传输质量最好的线路,如能用内网连通就不用公网。另外,如果尝试了很多线路都连通不了,那么它还会使用服务端中继的方式让双方连通
WebRTC 是如何做到的? 答案是采用多个 Candicate 排序并执行连通性测试的方式
ICE Candidate
它表示 WebRTC 与远端通信时使用的协议、IP 地址和端口, 一般有以下字段
其中:
-
host 表示 本机候选者
-
srflx 表示内网主机映射的外网的地址和端口(通过 TURN 服务器, 需要在 PeerConnectionConfig 里面去配)
-
relay 表示中继候选者 (通过 TURN 服务器, 需要在 PeerConnectionConfig 里面去配),
假设 A 和 B 都有多个 candidate, 那么 WebRTC 会按照host->srflx→relay的方式进行联通性测试, 选择最合适的方案
作为开发者, 只需要做两件事情:
1.部署相应的 TURN 服务器和 STUN 服务器(有现成的), 初始化 peerConnection 对象的时候配置 iceServers
- 监听 oniceccandidate 方法, 每接收到一个 candidate, 就通过信令服务器发送给另外一方即可
当我们没有设置 TURN 服务器或者 STUN 服务器的时候, 两个端建立起了连接, 就可以判断两个端位于同一个局域网内
STUN 服务器与 NAT 穿透
如果两台主机不在同一个内网, WebRTC 将尝试 NAT 打洞,即 P2P 穿越。WebRTC 将 NAT 分类为 4 种类型
-
完全锥型
-
NAT IP 限制型
-
NAT 端口限制型
-
NAT 对称型 NAT
具体的穿透逻辑也是对开发者屏蔽的, 但基本的思路是: 在公网上架设一台服务器,并向这台服务器发个请求, 该服务器往响应中塞入公网 IP , 这样客户端就可以知道自己的公网 IP
WebRTC 兼容性
从 can-i-use 中可以看到浏览器的支持情况还是相对乐观的
但是直接使用 webrtc 原生的 API 还是比较麻烦的, 虽然浏览器支持了 WebRTC, 但是各大浏览器内部的实现方式还是有差异, 需要对应的 pollyfill 方案
目前只有 adaptor.js 可以选择, 可以官方推荐的方案
基于原生 webrtc 的调用流程图
从上面的描述可以看到, 基于原生 WebRTC 去实现投屏功能的话还是比较麻烦的,
特别是 SDP 交换(createOffer 及 createAnswer)、网络候选信息收集(ICE candidate)
Peerjs
peerjs 简化了 webrtc 的开发过程,把 SDP 交换、ICE candidate 这些偏底层的细节都做了封装,开发人员只需要关注应用本身就行了。
peerjs 的核心对象 Peer,它有几个常用方法:
- peer.connect 创建点对点的连接
- peer.call 向另 1 个 peer 端发起音视频实时通信
- peer.on 对各种事件的监控回调
- peer.disconnect 断开连接
- peer.reconnect 重新连接
- peer.destroy 销毁对象
另外还有二个重要对象 DataConnection、MediaConnection,其中:
- DataConnection 用于收发数据(对应于 webrtc 中的 DataChannel),它的所有方法中有一个重要的 send 方法,用于向另一个 peer 端发送数据;
- MediaConnection 用于处理媒体流,它有一个重要的 stream 属性,表示关联的媒体流。
peerjs 内置了一套信令服务器, peer-server, 开发者可以自己部署, 不指定的情况下会使用 peerjs 官方托管的信令服务器
总结:
- 相比原生的方案, api 简化, 开发者关注底层的细节少了
- 提供了 peer-server 可供部署, 官方也提供免费托管的 peerServer
- 封装程度比较高, 后续改造会比较麻烦
Simple-peer
与 peerjs 类似, 把 SDP 交换、ICE candidate 这些偏底层的细节都做了封装, 但是封装程度没有 peerjs 那么高, 提供了一定的灵活性:
peer.signal(data): 发送信令
peer.send(data): 发送 data
peer.addStream(stream): 添加音视频流
peer.removeStream(stream): 移除音视频流
peer.addTrack(track, stream)
peer.removeTrack(track, stream)
peer.destroy([err]): 销毁实例
peer.on(): 监听各种事件
相比 peerjs, simple-peerjs 自己并不提供 peerServer 作为信令服务器, 而是提供了一个 signal 事件
peer.on("signal", (data) => {// when peer1 has signaling data, give it to peer2 somehow// 在这里使用IM发送信令
});
总结:
- 一定程度的封装, 相比原生不需要写很多冗余的样板代码,但也意味着一个高级功能的实现需要自己来实现(peerjs 有文件传输相关的实现)
- 相比 peerjs, 不依赖 peerServer, 可以接入我们的 IM
- 总代码不到一千行, 且不依赖 server 代码, 改造起来比较容易
一些注意事项
https 限制
由于浏览器的安全限制, 本地开发的时候需要使用 localhost 访问, 或者配置 https 证书, 不然无法拿到 navigator.mediaDevices 对象
参考
-
webrtc-samples
-
google webrtc
-
Peerjs
-
Simple-peer
-
从 0 打造音视频直播系统
本文首发于个人博客前端开发笔记,由于笔者能力有限,文章难免有疏漏之处,欢迎指正
相关文章:

WebRTC 初探
前言 项目中有局域网投屏与文件传输的需求,所以研究了一下 webRTC,这里记录一下学习过程。 WebRTC 基本流程以及概念 下面以 1 对 1 音视频实时通话案例介绍 WebRTC 的基本流程以及概念 WebRTC 中的角色 WebRTC 终端,负责音视频采集、编解码、NAT 穿…...

Python:read,readline和readlines的区别
在Python中,read(), readline(), 和 readlines() 是文件操作中常用的三个方法,它们都用于从文件中读取数据,但各自的使用方式和适用场景有所不同。 read() 方法: read(size-1) 方法用于从文件中读取指定数量的字符。如果指定了si…...

重生之我学编程
编程小白如何成为大神?大学新生的最佳入门攻略 编程已成为当代大学生的必备技能,但面对众多编程语言和学习资源,新生们常常感到迷茫。如何选择适合自己的编程语言?如何制定有效的学习计划?如何避免常见的学习陷阱&…...

如何将PostgreSQL的数据实时迁移到SelectDB?
PostgreSQL 作为一个开源且功能强大的关系型数据库管理系统,在 OLTP 系统中得到了广泛应用。很多企业利用其卓越的性能和灵活的架构,应对高并发事务、快速响应等需求。 然而对于 OLAP 场景,PostgreSQL 可能并不是最佳选择。 为了实现庞大规…...

关于c语言的const 指针
const * type A 指向的数据是常量 如上所示,运行结果如下,通过解引用的方式,改变了data的值 const type * A 位置是常量,不能修改 运行结果如下 type const * A 指针是个常量,指向的值可以改变 如上所示,…...

万能门店小程序开发平台功能源码系统 带完整的安装代码包以及安装搭建教程
互联网技术的迅猛发展和用户对于便捷性需求的不断提高,小程序以其轻量、快捷、无需安装的特点,成为了众多商家和开发者关注的焦点。为满足广大商家对于门店线上化、智能化管理的需求,小编给大家分享一款“万能门店小程序开发平台功能源码系统…...

C#初级——字典Dictionary
字典 字典是C#中的一种集合,它存储键值对,并且每个键与一个值相关联。 创建字典 Dictionary<键的类型, 值的类型> 字典名字 new Dictionary<键的类型, 值的类型>(); Dictionary<int, string> dicStudent new Dictionary<int, str…...

git版本控制的底层实现
目录 前言 核心概念串讲 底层存储形式探测 本地仓库的详细解析 提交与分支的深入解析 几个问题的深入探讨 前言 Git的重要性 Git是一个开源的版本控制工具,广泛用于编程开发领域。它极大地提高了研发团队的开发协作效率。对于开发者来说,Git是一个…...

深入解析数据处理的技术与实践
欢迎来到我的博客,很高兴能够在这里和您见面!欢迎订阅相关专栏: 工💗重💗hao💗:野老杂谈 ⭐️ 全网最全IT互联网公司面试宝典:收集整理全网各大IT互联网公司技术、项目、HR面试真题. ⭐️ AIGC时代的创新与未来:详细讲解AIGC的概念、核心技术、应用领域等内容。 ⭐…...

python-调用c#代码
环境: win10,net framework 4,python3.9 镜像: C#-使用IronPython调用python代码_ironpython wpf-CSDN博客 https://blog.csdn.net/pxy7896/article/details/119929434 目录 hello word不接收参数接收参数 其他例子 hello word 不…...

构建铁路安全防线:EasyCVR视频+AI智能分析赋能铁路上道作业高效监管
一、方案背景 随着我国铁路特别是高速铁路的快速发展,铁路运营里程不断增加,铁路沿线的安全环境对保障铁路运输的安全畅通及人民群众的生命财产安全具有至关重要的作用。铁路沿线安全环境复杂多变,涉及多种风险因素,如人员入侵、…...

openai command not found (mac)
题意:mac 系统上无法识别 openai 的命令 问题背景: Im trying to follow the fine tuning guide for Openai here. 我正在尝试遵循 OpenAI 的微调指南 I ran: 我运行以下命令 pip install --upgrade openaiWhich install without any errors.…...

鸿蒙(API 12 Beta2版)NDK开发【LLDB高性能调试器】调试和性能分析
概述 LLDB(Low Level Debugger)是新一代高性能调试器。 当前HarmonyOS中的LLDB工具是在[llvm15.0.4]基础上适配演进出来的工具,是HUAWEI DevEco Studio工具中默认的调试器,支持调试C和C应用。 工具获取 可通过HUAWEI DevEco S…...

HAL库源码移植与使用之DMA
内存到内存不支持传输计数器自动重装 结构: 与DMA具有连线的外设都可以完成搬运 DMA触发源 DMA优先级分配 由仲裁器来决定 寄存器作用: DMA.C #include "./BSP/DMA/dma.h" #include "./SYSTEM/delay/delay.h"DMA_HandleTypeDef…...

Scrapy爬虫框架介绍、创建Scrapy项目
Scrapy官网:https://scrapy.org/ 什么是Scrapy Scrapy 是一个基于 Python 的快速的高级网页抓取和网页爬取框架,用于抓取网站并从其页面中提取结构化数据。它可用于多种用途,从数据挖掘到监控和自动化测试。 Scrapy核心组件 1. Scrapy Engin…...

如何监测某个进程是否退出(C++)?
使用WaitForSingleObject函数,可以判断进程是否退出。 WaitForSingleObject函数的作用是:等待直到指定的对象处于信号状态(通知状态)或到达指定的等待时间(超时时间)。 函数声明如下: 1 DWOR…...

Python:Neo 库读取 ABF 文件,数据格式详解
Neo 库读取 ABF 文件后的数据格式 neo 是一个用于处理电生理数据的 Python 库,支持多种数据格式,包括 ABF 文件。了解 neo 读入 ABF 文件后的数据结构非常重要,以下给大家介绍一下使用 neo 读取 ABF 文件,及其对象格式。 1. ABF…...

【Linux】网络基础_3
文章目录 十、网络基础5. socket编程socket 常见APIsockaddr结构简单的UDP网络程序 未完待续 十、网络基础 5. socket编程 socket 常见API // 创建 socket 文件描述符 (TCP/UDP, 客户端 服务器) int socket(int domain, int type, int protocol);// 绑定端口号 (TCP/UDP, 服…...

C++之从C过渡(上)
C之从C过渡 前言 暂时告别C语言,我们走进C。对于有C语言基础,初学C的我们来说,在正式学习C的主体内容之前,我们需要先有一个过渡,本文中会总结过渡需要了解的零散知识,主要是语法。 正文 C的第一个程序 …...

MongoDB 100问
基础问题 1. 什么是MongoDB? MongoDB是一种面向文档的NoSQL数据库,使用BSON(二进制JSON)格式存储数据。它支持动态模式设计,具有高性能、高可用性和易扩展性。 2. MongoDB和传统关系型数据库的区别是什么?…...

Arduino ESP32使用 HardwareSerial创建一个任意串口
文章目录 前言使用 `HardwareSerial` 创建任意串口创建 `HardwareSerial` 对象示例代码`begin` 函数总结前言 在 Arduino 项目中,串口通信是一种常见且重要的通信方式。ESP32 作为一款功能强大的微控制器,提供了多个 UART(通用异步收发传输器)接口,允许用户灵活地进行串口…...

数据中台建设之数据存储
目录 一、技术选型需要明确的问题 二、技术选型需要考虑的几个方面 2.1 数据规模 2.2 数据生产方式 2.3 数据应用方式 三、技术选型的场景分析 3.1 概述 3.2 在线与离线 3.2.1 在线存储 3.2.2 离线存储 3.3 OLTP与OLAP 3.3.1 OLTP 3.3.2 OLAP 3.3.3 OLTP与OLAP的关…...

最常见的AI大模型总结
前言:大模型可以根据其主要的应领域和功能,可以分类为“文生文”(Text-to-Text)、“文生图”(Text-to-Image)和“文生视频”(Text-to-Video),都是基于自然语言处理&#…...

源码安装docker和docker-compose
前言 前提条件:内核要求 目前,CentOS 仅发行版本中的内核支持 Docker。 Docker 运行在 CentOS 7 上,要求系统为64位、系统内核版本为3.10 以上。 Docker 运行在 CentOS-6.5 或更高的版本的 CentOS 上,要求系统为64位、系统内核版…...

Java、PHP、Node 操作 MySQL 数据库常用方法
一、Java 操作 MySQL 数据库 1、Java 连接 MySQL 数据库 1. 使用 JDBC 驱动程序连接 使用这种方式,首先需要导入 MySQL 的 JDBC 驱动程序依赖,然后通过 Class.forName() 方法加载驱动程序类。其创建连接的过程相对直接,只需提供准确的数据库…...

nVisual分享社区正式上线啦!
nVisual分享社区正式上线啦! 访问地址:分享社区 nVisual是耐威迪基于数字孪生技术物联网技术开发的一款基础设施数字孪生软件工具,主要实现OSP室外与ISP室内基础设施的规划、记录、分析的可视化管理。 规划:nVisual可视化、智能化…...

4.5.门控循环单元GRU
门控循环单元GRU 对于一个序列,不是每个观察值都是同等重要的,可能会遇到一下几种情况: 早期观测值对预测所有未来观测值都具有非常重要的意义。 考虑极端情况,第一个观测值包含一个校验和,目的是在序列的末尾辨别…...

10种 Python数据结构,从入门到精通
今天我们将深入探讨 Python 中常用的数据结构,帮助你从基础到精通。每种数据结构都有其独特的特点和适用场景,通过实际代码示例和生活中的比喻,让你更容易理解这些概念。 学习数据结构的三个阶段 1、掌握基本用法:使用这些数据结…...

【AI】人工智能时代,程序员如何保持核心竞争力?
目录 程序员在AI时代的应对策略1. 引言2. AI在编程领域的影响2.1 AI辅助编程工具的现状2.2 AI对编程工作的影响2.3 程序员的机遇与挑战 3. 深耕细作:专注领域的深度学习3.1 专注领域的重要性3.2 深度学习的策略3.2.1 选择合适的领域3.2.2 持续学习和研究3.2.3 实践与…...

WPF学习(3)- WrapPanel控件(瀑布流布局)+DockPanel控件(停靠布局)
WrapPanel控件(瀑布流布局) WrapPanel控件表示将其子控件从左到右的顺序排列,如果第一行显示不了,则自动换至第二行,继续显示剩余的子控件。我们来看看它的结构定义: public class WrapPanel : Panel {pub…...