【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.pswp.cn/web/41376.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/41376.shtml
英文地址,請注明出處:http://en.pswp.cn/web/41376.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

avcodec_send_packet函數阻塞

用ffmpeg4.1.4開發一個播放器&#xff0c;解碼過程如下&#xff0c;在每個函數前設置標志&#xff0c;測試發現程序阻塞在avcodec_send_packet函數。 while(true){av_read_frameavcodec_send_packetavcodec_receive_frameav_packet_unref } 解釋如下&#xff1a; avcodec_se…

嵌入式Linux:如何學好嵌入式?

目錄 方法步驟 1、 基礎知識 2、 學習linux 3、 學習嵌入式linux 4、深入學習 嵌入式書籍推薦 Linux基礎 Linux內核 研發方向 硬件基礎 方法步驟 1、 基礎知識 目的:能看懂硬件工作原理,但重點在嵌入式軟件,特別是操作系統級軟件。 科目:數字電路、計算機組成原理…

Unity3D游戲 RPG

叢林探險游戲 人物進行探險游戲 擁有登錄&#xff0c;首頁&#xff0c;3D物體旋轉瀏覽的功能&#xff0c;還能進行種植樹等功能

【異常錯誤】‘NoneType‘ object has no attribute ‘GetSubstructMatches‘

出現的錯誤信息&#xff1a; AttributeError: Caught AttributeError in DataLoader worker process 0. Original Traceback (most recent call last): File "/home/mapengsen/anaconda3/envs//lib/python3.8/site-packages/torch/utils/data/_utils/worker.py", l…

【matlab 路徑規劃】基于改進遺傳粒子群算法的藥店配送路徑優化

一 背景介紹 本文分享的是一個基于訂單合并的訂單分配和路徑規劃聯合優化&#xff0c;主要背景是騎手根據客戶需求&#xff0c;從藥店取藥之后進行配送&#xff0c;配送的過程中考慮路徑的長度、客戶的服務時間窗、車輛的固定成本等要素&#xff0c;經過建模和優化得到最優的配…

C# WinForm —— 38 SplitContainer介紹

1. 簡介 將頁面拆分成兩個大小可以調整的區域&#xff0c;中間有一個拆分條&#xff0c;可以拖動拆分條來調整左右區域的大小 2. 屬性 屬性解釋(Name)控件ID&#xff0c;在代碼里引用的時候會用到BoderStyle邊框樣式&#xff1a;None、FixedSingle、Fixed3DAutoScroll當控件…

力扣 225 用隊列實現棧 記錄

題目描述 請你僅使用兩個隊列實現一個后入先出&#xff08;LIFO&#xff09;的棧&#xff0c;并支持普通棧的全部四種操作&#xff08;push、top、pop 和 empty&#xff09;。實現 MyStack 類&#xff1a; void push(int x) 將元素 x 壓入棧頂。 int pop() 移除并返回棧頂元素…

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 查殺-常規后門&…

linux內核定時器

文章目錄 一、jiffies定時器1.1 工作原理1.2 timer_list結構體1.3 相關接口1.3.1 初始化和啟動定時器1.3.2 修改定時器1.3.3 刪除定時器1.3.4 jiffies相關接口 二、高精度定時器2.1 hrtimer結構體2.2 相關接口2.2.1 初始化和啟動定時器2.2.2 取消定時器2.2.3 通過定時器實現周期…

shell-awk語法整理

shell-awk語法整理 前言基本語法內置變量1. $02. NF3. NR4. FS5. RS6. OFS7. ORS8. FILENAME9. FNR10. ARGV11. ENVIRON12. IGNORECASE13. RSTART 和 RLENGTH示例解釋 內置函數循環語句&#xff08;后面的;可不加&#xff09;條件語句高級特性示例 特殊模式BEGINEND組合示例BEG…

R語言實戰—圓形樹狀圖

話不多說&#xff0c;先看最終效果&#xff1a; 圓形樹狀圖是樹狀圖的一個變型&#xff0c;其實都是層次聚類。 接下來看代碼步驟&#xff1a; 首先要先安裝兩個包&#xff1a; install.packages("ggtree") install.packages("readxl") 咱就別問問什么…

29、php實現和為S的兩個數字(含源碼)

題目&#xff1a;php 實現 和為S的兩個數字 描述&#xff1a; 輸入一個遞增排序的數組和一個數字S&#xff0c;在數組中查找兩個數&#xff0c; 是的他們的和正好是S&#xff0c;如果有多對數字的和等于S&#xff0c;輸出兩個數的乘積最小的。 輸出描述&#xff1a; 對應每個測…