【WebRTC实现点对点视频通话】

介绍

WebRTC (Web Real-Time Communications) 是一个实时通讯技术,也是实时音视频技术的标准和框架。简单来说WebRTC是一个集大成的实时音视频技术集,包含了各种客户端api、音视频编/解码lib、流媒体传输协议、回声消除、安全传输等。对于开发者来说可以借助webrtc非常方便的实现低延时视频通话能力。目前大多主流的直播系统、会议系统基本都是基于WebRTC来实现。

三种架构

WebRTC针对不同场景以及性能考虑提供了三种架构Mesh架构、MCU、FSU。
在这里插入图片描述

Mesh架构

Mesh架构,需要所有参与连接的peer建立与所有其他peer的媒体连接(两两连接)。该架构需要n-1个上下行,以此带来的带宽消耗(流量)、编/解码消耗(设备性能)成线性增长。该架构只能适用3-4个人的小型会议场景。

MCU架构

所有参与连接的peer将本地媒体流推到远程媒体服务器,由媒体服务器进行混流,然后再推到所有连接的peer端。该架构的优点就是只需要1路上下行,随着peer人数不断增加,依然不会对用户造成带宽、手机性能影响。该架构将压力转嫁到服务端,由专用媒体服务器来完成混流,转推等功能。

SFU架构

相对于MCU来说SFU只做转发,媒体服务器压力有限。与Mesh架构相比,只需要n-1个下行,1个上行,减少了服务器压力。在大规模的场合该架构具有伸缩性。

点对点视频连接

根据上面,我们对基本WebRTC有了最基本的认识,下面就从点对点实际例子来从代码角度进一步了解其原理。

先从下图来看看使用MCU来实现点对点需要哪些东西在这里插入图片描述
在介绍流程之前,先简单介绍下上图中出现的名词代表什么意思:

  • Peer:通信双方设备。
  • Signaling Server: 信令服务器,用于交互连接双方的信令数据(SDP、ICE等),以保证通信的对等连接建立。
  • NAT:处理私有网络和公共网络之间的地址转换问题(因为大多数设置都处于内网中,需要转换为公共网络才能进行外网访问)
  • STUN:用于发现设备的公共地址(通过NAT转换的公网地址),辅助穿越NAT进行点对点连接。
  • TURN:在无法建立直接连接时提供数据中继,确保通信的可靠性。对等连接异常时的兜底方案。
  • SDP:会话描述协议,用于描述和协商媒体会话的协议,它定义了会话的所有技术细节,包括媒体格式、编解码器、网络地址等。,
  • ICE:用于发现和选择最优网络路径的框架,确保在各种网络环境下都能成功建立和维持连接。

代码实现

实现点对点连接主要是两点:1、信令数据交互 2、对等连接建立
在代码中使用到了socket.io来将设备和信令服务器通信,使用了simple-peer来建立对等连接,由于该demo在本地运行所以没有使用STUN/TRUN服务器,有兴趣的可以使用Chrome提供的公共服务器stun:stun.l.google.com:19302
主要步骤如下:

  • 1、和信令服务器建立连接,并获取自身的socketId作为唯一标识
  • 2、申请方将信令(由simple-peer生成)通过信令服务器到达接受方
  • 3、接受方接受,将发起方的信令保存到对等连接peer中,并且将自己的信令通过信令服务器给到发送方
  • 4、发送方将接受方的信令数据保存到对等连接peer中,至此发送方-接受方对等连接建立完成
  • 5、在发送方和接受方监听peer的stream,来获取视频流,然后展示在页面

和信令服务器建立连接

新建一个server,js使用node+express搭建的简易信令服务器,用于交换双方信令。通过create-react-app来创建一个前端页面。

信令服务器代码如下:

const express = require("express");
const http = require("http");
const cors = require("cors");const app = express();
const server = http.createServer(app);
app.use(cors);
const io = require("socket.io")(server, {cors: {origin: "*",methods: ["POST", "GET"],},
});server.listen(5001, () => {console.log("listening on 5000 ...");
});io.on("connection", (socket) => {// 分发socket idsocket.emit("offer", socket.id);// 发送发起方的信令数据别answersocket.on("callUser", (data) => {io.to(data.answerId).emit("callUser", data);});// 发送接收放信令给申请方socket.on("answerSignalInfo", (data) => {io.to(data.to).emit("answerSignalInfo", data);});socket.on("disconnect", () => {socket.broadcast.emit("callEnded", socket);});
});
// frontend
// 通过socket.io和服务器进行连接
const socket = io("http://localhost:5001");// 获取自身的socket id
socket.on("offer", (offerId) => {console.log("offer socket ID", offerId);setOfferId(offerId);getLocalStream(); // 获取本地视频流
});

传递信令数据

// 通过simple-peer 交换信令数据 offer -> 信令服务器 -> answer
const peer = new Peer({initiator: true, // 是否是发起方stream: localStream, // 传递的视频流trickle: false, // 点对点传输,获取单个信号// 设置STUN服务器,Chrome提供的公共服务器config: {iceServers: [{ urls: "stun:stun.l.google.com:19302" }],},
});
peer.on("signal", (data: any) => {socket.emit("callUser", {singleData: data, // 发送通话方的信令数据answerId: answerId, // 需要和谁通话from: offerId, // 谁申请通话});
});

接收信令数据

接收方接收发起方的信令数据,并保存到Peer中,然后将自身的信令数据返回给发起方

const peer = new Peer({initiator: false,stream: localStream,trickle: false,config: {iceServers: [{ urls: "stun:stun.l.google.com:19302" }],},
});
peer.on("signal", (data) => {socket.emit("answerSignalInfo", {answerSignalInfo: data,to: offerUserInfo?.id,from: offerId,});
});if (offerUserInfo?.singleData) {peer.signal(offerUserInfo.singleData);
}

对等连接建立,获取双方视频流

交互信令之后,通过simple-peer成功建立对等连接,监听stream视频流然后显示在页面上

// 监听通过对等连接传递的stream
peer.on("stream", (stream) => {if (remoteVideoRef.current) {remoteVideoRef.current.srcObject = stream;remoteVideoRef.current.play();}
});

完整页面代码:CSS样式文件省略

import React, { useCallback, useEffect, useRef, useState } from "react";
import { io } from "socket.io-client";
import Peer from "simple-peer";
import "./App.css";const socket = io("http://localhost:5001");type UserInfo = {singleData: any;id: string;
};function App() {// 用于引用 DOM 元素const localVideoRef = useRef<HTMLVideoElement>(null);const remoteVideoRef = useRef<HTMLVideoElement>(null);// 用于管理状态const [localStream, setLocalStream] = useState<MediaStream | undefined>();const [offerId, setOfferId] = useState("");const [answerId, setAnswerId] = useState("");const [offerUserInfo, setOfferUserInfo] = useState<UserInfo>();// 获取本地视频流const getLocalStream = useCallback(async () => {try {const stream = await navigator.mediaDevices.getUserMedia({video: {width: { ideal: 200 }, // 理想的宽度height: { ideal: 200 }, // 理想的高度},audio: false,});console.log("local media", stream);setLocalStream(stream);if (localVideoRef.current) {localVideoRef.current.srcObject = stream;}} catch (error) {console.error("Error accessing media devices.", error);}}, []);// 手动设置通话方idconst onChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {console.log("onChange call id", e);setAnswerId(e.target.value);}, []);// 获取信令牌服务器发送的socket idconst init = useCallback(() => {socket.on("offer", (offerId) => {console.log("offer socket ID", offerId);setOfferId(offerId);getLocalStream(); // 获取本地视频流});// 监听信令服务器发送的通话申请方的信令牌数据socket.on("callUser", ({ singleData, answerId, from }) => {console.log(`${from}发起通话`, from);setOfferUserInfo({singleData: singleData,id: from,});});}, [getLocalStream]);// 创建和发送 offerconst startCall = useCallback(async () => {// 通过simple-peer 交换信令数据 offer -> 信令服务器 -> answerconst peer = new Peer({initiator: true,stream: localStream,trickle: false,// 设置STUN服务器,Chrome提供的公共服务器config: {iceServers: [{ urls: "stun:stun.l.google.com:19302" }],},});peer.on("signal", (data: any) => {socket.emit("callUser", {singleData: data, // 发送通话方的信令数据answerId: answerId, // 需要和谁通话from: offerId, // 谁申请通话});});// 获取到接收方的信令数据socket.on("answerSignalInfo", (data) => {console.log(`${data.from}已经接受通话`, data, peer);peer.signal(data.answerSignalInfo);});// 监听通过对等连接传递的streampeer.on("stream", (stream) => {if (remoteVideoRef.current) {remoteVideoRef.current.srcObject = stream;remoteVideoRef.current.play();}});// setPeer(peer);}, [answerId, localStream, offerId, remoteVideoRef]);const acceptCall = useCallback(() => {const peer = new Peer({initiator: false,stream: localStream,trickle: false,config: {iceServers: [{ urls: "stun:stun.l.google.com:19302" }],},});peer.on("signal", (data) => {socket.emit("answerSignalInfo", {answerSignalInfo: data,to: offerUserInfo?.id,from: offerId,});});if (offerUserInfo?.singleData) {peer.signal(offerUserInfo.singleData);}// 监听通过对等连接传递的streampeer.on("stream", (stream) => {if (remoteVideoRef.current) {remoteVideoRef.current.srcObject = stream;remoteVideoRef.current.play();}});}, [localStream, offerUserInfo, offerId, remoteVideoRef]);useEffect(() => {init();}, [init]);return (<div className="App"><video autoPlay muted ref={localVideoRef} className="video" /><video autoPlay muted ref={remoteVideoRef} className="video" /><input value={answerId} onChange={onChange} placeholder="call id" /><button onClick={startCall}>发起通话</button><button onClick={acceptCall}>同意通话</button></div>);
}export default App;

至此可以启动项目,并本地浏览器打开两个tab即可体验点对点视频服务。

总结

点对点通信,主要就是信令数据的交换,知道通信双方具体的配置信息(通信参数、IP地址等)以保证对等连接的成功建立,然后传递视频流在页面展示。
其中信令服务器仅用于对等连接前的信令交换,不会进行数据传输。NAT是将设备内网地址转换为外网公共地址。STUN来获取设置的公网地址。TURN服务器是用于对等连接异常时的兜底方案,可进行数据传输。

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

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

相关文章

Unity3D游戏 RPG

丛林探险游戏 人物进行探险游戏 拥有登录&#xff0c;首页&#xff0c;3D物体旋转浏览的功能&#xff0c;还能进行种植树等功能

【matlab 路径规划】基于改进遗传粒子群算法的药店配送路径优化

一 背景介绍 本文分享的是一个基于订单合并的订单分配和路径规划联合优化&#xff0c;主要背景是骑手根据客户需求&#xff0c;从药店取药之后进行配送&#xff0c;配送的过程中考虑路径的长度、客户的服务时间窗、车辆的固定成本等要素&#xff0c;经过建模和优化得到最优的配…

C# WinForm —— 38 SplitContainer介绍

1. 简介 将页面拆分成两个大小可以调整的区域&#xff0c;中间有一个拆分条&#xff0c;可以拖动拆分条来调整左右区域的大小 2. 属性 属性解释(Name)控件ID&#xff0c;在代码里引用的时候会用到BoderStyle边框样式&#xff1a;None、FixedSingle、Fixed3DAutoScroll当控件…

C++ 引用做函数返回值

作用&#xff1a;引用是可以作为函数的返回值存在的 注意&#xff1a;不要返回局部变量引用 用法&#xff1a;函数调用作为左值 示例&#xff1a; 运行结果&#xff1a;

程序员熬夜看欧洲杯被“冻住”,呼吸困难……

2024欧洲杯接近尾声&#xff0c;更是激发球迷兴趣。由于时差关系&#xff0c;很多球迷熬夜看球&#xff0c;啤酒、宵夜成了标配。然而&#xff0c;在这份欢乐背后&#xff0c;也隐藏着健康风险。 日前&#xff0c;浙江杭州29岁的程序员单先生熬夜与朋友看完球赛后开车回家&…

零基础STM32单片机编程入门(九)IIC总线详解及EEPROM实战含源码视频

文章目录 一.概要二.IIC总线基本概念1.总体特征2.通讯流程 三.EEPROM介绍1.M24C08基本介绍2.向M24C08写一个字节时序图3.从M24C08读一个字节时序图 四.GPIO模拟IIC驱动M24C08读写五.CubeMX工程源代码下载六.讲解视频链接地址七.小结 一.概要 IIC(Inter&#xff0d;Integrated …

黑马|最新AI+若依 |初识项目

本章主要内容是&#xff1a; 1.快速搭建了若依前后端项目在本地 2.实现了单表的增删改查快速生成 文章目录 介绍1.若依介绍2.若依的不同版本3.项目运行环境 初始化前后端项目1.下载若依项目2.初始化后端a.把表导入到数据库中b.更改application.yml文件 3.初始化前端a.安装依赖…

基于LoFTR_TRT项目实现LoFTR模型的trt推理与onnx推理,3060显卡下320图像30ms一组图

本博文主要记录了使用LoFTR_TRT项目将LoFTR模型导出为onnx模型&#xff0c;然后将onnx模型转化为trt模型。并分析了LoFTR_TRT与LoFTR的基本代码差异&#xff0c;但从最后图片效果来看是与官网demo基本一致的&#xff0c;具体可以查看上一篇博客记录。最后记录了onnx模型的使用【…

WebAssembly场景及未来

引言 从前面的文章中&#xff0c;我们已经了解了 WebAssembly&#xff08;WASM&#xff09; 的基本知识&#xff0c;演进历程&#xff0c;以及简单的使用方法。通过全面了解了WebAssembly的设计初衷和优势&#xff0c;我们接下来要知道在什么样的场景中我们会使用 WASM 呢&…

在门店里造绿色氧吧!康养行业也这么卷了?

拼啥不如拼健康&#xff0c;现在的人算是活明白了&#xff0c;不但中老年人这样想&#xff0c;年轻人也这样干。你可能不知道&#xff0c;现在众多健康养生门店&#xff0c;逐渐成了年轻人“组团养生”的好去处&#xff0c;也是他们吃喝玩乐之外的新兴消费趋势。 而在看得见的…

原理图设计工作平台:capture和capture CIS的区别在于有没有CIS模块

1环境:design entry CIS 2.参数设置命令options——preference&#xff08;7个选项卡colors/print&#xff0c;grid display&#xff0c;miscellaneous&#xff0c;pan and zoom&#xff0c;select&#xff0c;text editor和board simulation&#xff09; 1)颜色设置colors/p…

应急响应--网站(web)入侵篡改指南

免责声明:本文... 目录 被入侵常见现象: 首要任务&#xff1a; 分析思路&#xff1a; 演示案例: IIS&.NET-注入-基于时间配合日志分析 Apache&PHP-漏洞-基于漏洞配合日志分析 Tomcat&JSP-弱口令-基于后门配合日志分析 (推荐) Webshell 查杀-常规后门&…

R语言实战—圆形树状图

话不多说&#xff0c;先看最终效果&#xff1a; 圆形树状图是树状图的一个变型&#xff0c;其实都是层次聚类。 接下来看代码步骤&#xff1a; 首先要先安装两个包&#xff1a; install.packages("ggtree") install.packages("readxl") 咱就别问问什么…

go zero入门

一、goctl安装 goctl 是 go-zero 的内置脚手架&#xff0c;可以一键生成代码、文档、部署 k8s yaml、dockerfile 等。 # Go 1.16 及以后版本 go install github.com/zeromicro/go-zero/tools/goctllatest检查是否安装成功 $ goctl -v goctl version 1.6.6 darwin/amd64vscod…

vue2响应式原理+模拟实现v-model

效果 简述原理 配置对象传入vue实例 模板解析&#xff0c;遍历出所有文本节点&#xff0c;利用正则替换插值表达式为真实数据 data数据代理给vue实例&#xff0c;以后通过this.xxx访问 给每个dom节点增加观察者实例&#xff0c;由观察者群组管理&#xff0c;内部每一个键值…

sqlite 数据库 介绍

文章目录 前言一、什么是 SQLite &#xff1f;二、语法三、SQLite 场景四、磁盘文件 前言 下载 目前已经出到了&#xff0c; Version 3.46.0 SQLite&#xff0c;是一款轻型的数据库&#xff0c;是遵守ACID的关系型数据库管理系统&#xff0c;它包含在一个相对小的C库中。它是…

VMware虚拟机配置桥接网络

转载&#xff1a;虚拟机桥接网络配置 一、VMware三种网络连接方式 VMware提供了三种网络连接方式&#xff0c;VMnet0, VMnet1, Vmnet8&#xff0c;分别代表桥接&#xff0c;Host-only及NAT模式。在VMware的编辑-虚拟网络编辑器可看到对应三种连接方式的设置&#xff08;如下图…

Square Root SAM论文原理

文章目录 Square Root SAM论文原理核心原理SLAM问题的3种表示贝叶斯网络因子图&#xff08;Factor graph&#xff09;马尔科夫随机场(Markov Random Field, MRF) SLAM最小二乘问题&线性化因式分解 factorization矩阵与图(Matrices ⇔ Graphs)因式分解&变量消元(Factori…

Kafka系列之Kafka知识超强总结

一、Kafka简介 Kafka是什么 Kafka是一种高吞吐量的分布式发布订阅消息系统&#xff08;消息引擎系统&#xff09;&#xff0c;它可以处理消费者在网站中的所有动作流数据。 这种动作&#xff08;网页浏览&#xff0c; 搜索和其他用户的行动&#xff09;是在现代网络上的许多社…

14-22 剑和远方2 - 深度神经网络中的学习机制

概论 在第一部分中&#xff0c;我们深入探讨了人工智能的兴衰简史以及推动人工智能发展的努力。我们研究了一个简单的感知器&#xff0c;以了解其组件以及简单的 ANN 如何处理数据和权重层。在简单的 ANN 中&#xff0c;不会对数据执行特定操作。ANN 中的激活函数是一个线性函…