【WebRTC】音视频通信

news2024/11/24 23:31:56

WebRTC对等体还需要查找并交换本地和远程音频和视频媒体信息,例如分辨率和编解码器功能。 交换媒体配置信息的信令通过使用被称为SDP的会话描述协议格式来交换,被称为提议和应答的元数据块

WebRTC 音视频通信基本流程

  • 一方发起调用 getUserMedia 打开本地摄像头
  • 媒体协商(信令交换,媒体协商主要指 SDP 交换。)
  • 建立通信

发起端 Amy 创建 Offer 并将 Offer 信息,并调用 setLocalDescription 将其保存起来,通过信令服务器传送给接收端 Bob
接收端 Bob 收到对等端 Amy 的 Offer 信息后调用 setRemoteDescription 方法将其保存起来,并创建 Answer 信息,同理也将 Answer 消息通过 setLocalDescription 保存,并通过信令服务器传送给呼叫端 Amy
呼叫端 Amy 收到对等端 Blob 的 Answer 信息后调用 setRemoteDescription 方法将其 Answer 保存起来

为什么需要媒体协商?

媒体协商的作用就是让双方找到共同支持的媒体能力,从而能实现彼此之间的音视频通信。比如两个人想聊天,一个人只会讲中文,喜欢讨论前端;一个人只会讲英文,不喜欢前端技术;通过互换资料,发现这没法聊天。但如果出现另一个人会讲中文,喜欢研究代码和前端技术,跟第一个人交换信息后,发现这可以聊天。所以发起端与接收端能不能进行通信。

媒体协商是在做什么?

媒体协商就是在交换 SDP的过程。会话发起者通过创建一个offer,经过信令服务器发送到接收方,接收方创建answer并返回给发送方,完成交换。

SDP 是什么?

SDP(Session Description Protocol)指会话描述协议,是一种通用的协议,基于文本,其本身并不属于传输协议,需要依赖其它的传输协议(如 RTP 交换媒体信息。

SDP 主要用来描述多媒体会话,用途包括会话声明、会话邀请、会话初始化等。通俗来讲,它可以表示各端的能力,记录有关于你音频编解码类型、编解码器相关的参数、传输协议等信息。

交换 SDP 时,通信的双方会将接受到的 SDP 和自己的 SDP 进行比较,取出他们之间的交集,这个交集就是协商的结果,也就是最终双方音视频通信时使用的音视频参数及传输协议。

offer 和 answer 是什么?

在双方要建立点对点通信时,发起端发送的 SDP 消息称为 Offer,接收端发送的 SDP 消息称为 Answer
所以,offer 和 answer 本质就是存有 SDP 信息的对象,所以也会叫做 SDP Offer 和 SDP Answer。

信令与信令服务器

信令通常指的是为了网络中各种设备协调运作,在设备之间传递的控制信息。

对于 WebRTC 通信来说,发起端发送 Offer SDP 和接收端接受 Answer
SDP,要怎么发给对方呢?这个过程还需要一种机制来协调通信并发送控制消息,这个过程就称为信令。

而信令对应的服务器就叫信令服务器,作为中间人帮助建立连接,主要负责:
信令的处理,如媒体协商消息的传递

管理房间信息。比如用户连接时告诉信令服务器自身的房间号,由信令服务器找到也在该房间号的对等端并开始尝试通信,也通知用户谁加入了房间和离开了房间,通知房间人数是否已满等等,所以也叫信令服务器也叫房间服务器。

WebRTC 并没有规定信令必须使用何种实现,目前业界使用较多的是 WebSocket + JSON/SDP 的方案。其中 WebSocket 用来提供信令传输通道,JSON/SDP 用来封装信令的具体内容。

ICE

当媒体协商完成后,WebRTC 就开始建立网络连接,其过程称为 ICE(Interactive Connectivity Establishment)交互式连接建立。

ICE 不是一种协议,整合了 STUN 和 TURN 两种协议(用于 NAT 穿透)的框架。

ICE 是在各端调用 setLocalDescription() 后就开始了,其操作过程如下:

  • 收集 Candidate
  • 交换 Candidate
  • 按优先级尝试连接

什么是 Candidate?

比如想用 socket 连接某台服务器,一定要知道这台服务器的一些基本信息,如服务器的 IP 地址、端口号以及使用的传输协议。只有知道了这些信息,才能与这台服务器建立连接。而 Candidate 正是 WebRTC 用来描述它可以连接的远端的基本信息,因此 Candidate 是至少包括 IP 地址、端口号、协议的一个信息集。

const peerA = new RTCPeerConnection()
const peerB = new RTCPeerConnection()
API
pc.createOffer:创建 offer 方法,方法会返回 SDP Offer 信息
pc.setLocalDescription 设置本地 SDP 描述信息
pc.setRemoteDescription:设置远端的 SDP 描述信息,即对方发过来的 SDP 信息
pc.createAnswer:远端创建应答 Answer 方法,方法会返回 SDP Offer 信息
pc.ontrack:设置完远端 SDP 描述信息后会触发该方法,接收对方的媒体流
pc.onicecandidate:设置完本地 SDP 描述信息后会触发该方法,打开一个连接,开始运转媒体流
pc.addIceCandidate:连接添加对方的网络信息
pc.setLocalDescription:将 localDescription 设置为 offer,localDescription 即为我们需要发送给应答方的 sdp,此描述指定连接本地端的属性,包括媒体格式
pc.setRemoteDescription:改变与连接相关的描述,该描述主要是描述有些关于连接的属性,例如对端使用的解码器

RTCPeerConnection就代表着一个peer的连接,它是WebRTC应用核心。

  • 1、调用peerA (RTCPeerConnection对象) createOffer方法准备创建SDP
  • 2、在createOffer的回调方法里,同时做了这两件事

a、调用peerA的setLocalDescription(description)方法,这个方法会触发peerA的icecandidate
监听方法handleConnection. 在这个方法里,会将peerA的icecandidate发送给peerB.
然后PeerB执行addIceCandidate(candidate),将peerA的candidate登记在案.
b、将peerA的description (就是SDP)发送给peerB

  • 3、peerB收到peerA发来的SDP,执行createAnswer,在这个回调方法里,同时做两件事

a、调用peerB的setLocalDescription(description)方法,这个方法会触发peerB的icecandidate监听方法handleConnection,在这个方法里,会将peerB的icecandidate发送给peerA.
peerA收到后执行addIceCandidate(candidate),将peerB的candidate也登记
b、将peerB的SDP发送给peerA.

  • 4、peerA和peerB开始传递音视频

总结:整个过程就是peerA和peerB互相交换iceCandidate和SDP的过程。
在这里插入图片描述

import { useEffect, useRef, useState } from 'react'
import './App.css'

function App() {
  const localVideoRef = useRef<HTMLVideoElement>(null)
  const remoteVideoRef = useRef<HTMLVideoElement>(null)
  const pc = useRef<RTCPeerConnection>()
  const localStreamRef = useRef<MediaStream>()
  const wsRef = useRef(new WebSocket('ws://127.0.0.1:1234'))
  const username = (Math.random() + 1).toString(36).substring(7)
  const [status, setStatus] = useState('开始通话')

  useEffect(() => {
    initWs()
    getMediaDevices().then(() => {
      createRtcConnection()
      addLocalStreamToRtcConnection()
    })
  }, [])

  const initWs = () => {
    wsRef.current.onopen = () => console.log('ws 已经打开')
    wsRef.current.onmessage = wsOnMessage
  }

  const wsOnMessage = (e: MessageEvent) => {
    const wsData = JSON.parse(e.data)
    console.log('wsData', wsData)

    const wsUsername = wsData['username']
    console.log('wsUsername', wsUsername)
    if (username === wsUsername) {
      console.log('跳过处理本条消息')
      return
    }

    const wsType = wsData['type']
    console.log('wsType', wsType)

    if (wsType === 'offer') {
      const wsOffer = wsData['data']
      pc.current?.setRemoteDescription(new RTCSessionDescription(JSON.parse(wsOffer)))
      setStatus('请接听通话')
    }
    if (wsType === 'answer') {
      const wsAnswer = wsData['data']
      pc.current?.setRemoteDescription(new RTCSessionDescription(JSON.parse(wsAnswer)))
      setStatus('通话中')
    }
    if (wsType === 'candidate') {
      const wsCandidate = JSON.parse(wsData['data'])
      pc.current?.addIceCandidate(new RTCIceCandidate(wsCandidate))
      console.log('添加候选成功', wsCandidate)
    }
  }

  const wsSend = (type: string, data: any) => {
    wsRef.current.send(JSON.stringify({
      username,
      type,
      data,
    }))
  }

  const getMediaDevices = async () => {
    const stream = await navigator.mediaDevices.getUserMedia({
      video: true,
      audio: false,
    })
    console.log('stream', stream)
    localVideoRef.current!.srcObject = stream
    localStreamRef.current = stream
  }

  const createRtcConnection = () => {
    const _pc = new RTCPeerConnection({
      iceServers: [
        {
          urls: ['stun:stun.stunprotocol.org:3478'],
        }
      ]
    })
    _pc.onicecandidate = e => {
      if (e.candidate) {
        console.log('candidate', JSON.stringify(e.candidate))
        wsSend('candidate', JSON.stringify(e.candidate))
      }
    }
    _pc.ontrack = e => {
      remoteVideoRef.current!.srcObject = e.streams[0]
    }
    pc.current = _pc
    console.log('rtc 连接创建成功', _pc)
  }

  const createOffer = () => {
    pc.current?.createOffer({
      offerToReceiveVideo: true,
      offerToReceiveAudio: true,
    })
      .then(sdp => {
        console.log('offer', JSON.stringify(sdp))
        pc.current?.setLocalDescription(sdp)
        wsSend('offer', JSON.stringify(sdp))
        setStatus('等待对方接听')
      })
  }

  const createAnswer = () => {
    pc.current?.createAnswer({
      offerToReceiveVideo: true,
      offerToReceiveAudio: true,
    })
      .then(sdp => {
        console.log('answer', JSON.stringify(sdp))
        pc.current?.setLocalDescription(sdp)
        wsSend('answer', JSON.stringify(sdp))
        setStatus('通话中')
      })
  }

  const addLocalStreamToRtcConnection = () => {
    const localStream = localStreamRef.current!
    localStream.getTracks().forEach(track => {
      pc.current!.addTrack(track, localStream)
    })
    console.log('将本地视频流添加到 RTC 连接成功')
  }

  return (
    <div>
      <div>{`username:${username}`}</div>
      <video style={{ width: '400px' }} ref={localVideoRef} autoPlay controls></video>
      <video style={{ width: '400px' }} ref={remoteVideoRef} autoPlay controls></video>
      <br />
      <p>{`当前状态:${status}`}</p>
      <br />
      {status === '开始通话' && (
        <button onClick={createOffer}>拨号</button>
      )}
      {status === '请接听通话' && (
        <button onClick={createAnswer}>接听</button>
      )}
    </div>
  )
}

export default App

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

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

相关文章

线程池在业务中的实践-美团技术团队分享

原文地址&#xff1a;Java线程池实现原理及其在美团业务中的实践 场景1&#xff1a;快速响应用户请求 描述&#xff1a;用户发起的实时请求&#xff0c;服务追求响应时间。比如说用户要查看一个商品的信息&#xff0c;那么我们需要将商品维度的一系列信息如商品的价格、优惠、…

从小白到大神之路之学习运维第31天

第二阶段基础 时 间&#xff1a;2023年5月29日 参加人&#xff1a;全班人员 内 容&#xff1a; Rsync服务 目录 一、基本信息 二、rsync命令 三、rsyncinotfy实时同步 一、基本信息 &#xff08;一&#xff09;概述 rsync是linux 下一个远程数据同步工具 他可通过…

拼多多获取整站实时商品详情数据|商品标题|商品链接,数据采集,数据分析提取教程

拼多多是一个基于社交电商的购物平台&#xff0c;它通过通过价格和优惠吸引大量用户&#xff0c;使用户形成消费场景和消费共同体&#xff0c;最终实现规模效应。在拼多多运营中&#xff0c;API接口起到了重要的作用&#xff0c;它可以实现不同系统之间的信息共享和数据传递&am…

EasyDSS使用OBS推流成功,但不显示播放按钮是什么原因?

EasyDSS支持一站式的上传、转码、直播、回放、嵌入、分享功能&#xff0c;具有多屏播放、自由组合、接口丰富等特点。平台可以为用户提供专业、稳定的直播推流、转码、分发和播放服务&#xff0c;全面满足超低延迟、超高画质、超大并发访问量的要求。 有用户反馈&#xff0c;使…

如何找到高清照片,4K高清风景照片,建议收藏!

想找些高清的风景图来做本个性杂志给朋友或者只是想用做壁纸使用&#xff0c;结果崩溃于互联网图片的图海中。 如何找到高清照片&#xff1f; 1. 图片分享网站 如果你想查找高清的风景图片&#xff0c;那么图片分享网站是一个不错的选择。这些网站通常提供大量的高清图片供用…

VS2019 WPF制作OTA上位机(三)串口打开

先在UI上添加控件 首先&#xff0c;改变一下原来的方法&#xff0c; 原来的三个控件是没有布局的&#xff0c;添加一下布局。 布局用简单的行布局&#xff0c;也就是说从&#xff0c;上到下&#xff0c;分成一行一行的&#xff0c;如下图 将上一篇文章的代码修改 <Window …

【MYSQL】通过存储过程调用事务方法

假设有表test_1&#xff1a; BEGINDECLARE err int DEFAULT 0;declare continue handler for sqlexception set err1;#当sqlexception handler捕捉到异常时&#xff0c;设置err1START TRANSACTION;#开始事务update test_1 set value 50 where id 58;IF (err0) THENcommit;#增…

打包ios-App之使用Appuploader

appuploader教程 一.申请个人开放者账号&#xff1a;https://idmsa.apple.com 网站内申请即可 注意&#xff1a;申请付费开发者账号需要付费688&#xff0c;付费之后就直接申请证书即可 未付费 二.申请ios测试证书&#xff08;p12&#xff09; 1.打开Appuploader&#xff0c;用…

建行对接微信支付

1、获取配置信息&#xff0c;基础代码设置 1.1 建行支付、退款需要商户提供以下信息&#xff1a; 商户代码支付使用商户柜台代码支付使用分行代码支付使用公钥外联平台使用、支付使用操作员号外联平台使用操作员号交易密码外联平台使用证书外联平台使用证书密码外联平台使用 …

python-import request失败

mac电脑 vscode。 &#xff01;&#xff01;&#xff01;踩坑&#xff0c;搞了2天 烦了哦 1&#xff1a;python安装&#xff1a; 下载地址&#xff1a;https://cdn.npmmirror.com/binaries/python/3.12.0/python-3.12.0a7-macos11.pkg 2: python配置PATH terminal指令打which …

JVM学习(十二):执行引擎

目录 一、执行引擎概述 二、执行引擎的工作过程 三、Java代码编译和执行 3.1 过程概述 3.1 javac前端编译 3.2 Java字节码的执行 3.3 编译和解释概述 3.4 高级语言理解与执行过程&#xff08;机器码、指令、汇编&#xff09; 3.4.1 机器码 3.4.2 指令 3.4.3 指…

Apache的配置、应用和优化(遥不可及)

文章目录 一、构建虚拟web主机二、配置虚拟主机1.基于域名&#xff08;1&#xff09;为虚拟主机提供域名解析&#xff08;2&#xff09;为虚拟主机准备网页文档&#xff08;3&#xff09;添加虚拟主机配置&#xff08;5&#xff09;启用上一步的子配置文件&#xff08;6&#x…

Python来写一个童话故事

Python来写一个童话故事 主题&#xff1a;冒险&#xff0c; 风格&#xff1a;惊险&#xff0c; 人物&#xff1a;男孩&#xff0c; 地点&#xff1a;海底。 循环遍历鱼列表中的每一条鱼 for fish in fishes:# 获取男孩和鱼的坐标和距离boy_x, boy_y boy.position()fish_x, …

亿发生产管理信息化系统,生产制造型企业信息化建设

在不断发展的先进制造格局中&#xff0c;传统生产管理模式的固化限制了企业规模化生产能力。为适应这个充满活力的时代需求&#xff0c;实现战略目标&#xff0c;企业必须借助信息技术的力量加强生产过程管理&#xff0c;踏上企业生产信息化的征程。亿发生产管理信息化系统&…

Python3数据分析与挖掘建模(4)集中趋势与离中趋势、数据分布与抽样

分析理论是统计学和数据分析中的重要概念&#xff0c;它们用于描述和理解数据的集中趋势、离中趋势、数据分布以及抽样理论。下面是对这些概念的简要说明&#xff1a; 集中趋势&#xff1a; 均值、中位数与分位数、众数离中趋势&#xff1a;标准差、方差数据分布&#xff1a;偏…

[Nacos] Nacos Server与Nacos Client间的UDP通信 (十)

文章目录 1.Nacos Server与Nacos Client间的UDP通信1.1 Nacos Server向Nacos Client进行UDP推送1.2 Nacos Client接收Nacos Server的UDP推送 1.Nacos Server与Nacos Client间的UDP通信 Nacos Server向Nacos Client进行UDP推送Nacos Client接收Nacos Server的UDP推送 1.1 Naco…

黑客常用工具合集

首先恭喜你发现了宝藏。 本文章集成了全网优秀的开源攻防武器项目&#xff0c;包含&#xff1a; 信息收集工具&#xff08;自动化利用工具、资产发现工具、目录扫描工具、子域名收集工具、指纹识别工具、端口扫描工具、各种插件....etc...&#xff09;漏洞利用工具&#xff0…

枚举_源码_分析

枚举源码分析 前言 这是所有Java语言枚举类型的公共基类。关于枚举的更多信息&#xff0c;包括编译器合成的隐式声明方法的描述&#xff0c;可以在Java的第8.9节中找到™ 语言规范。 请注意&#xff0c;当使用枚举类型作为集合的类型或映射中键的类型时&#xff0c;可以使用专…

[NOIP2004 普及组] FBI 树 队列解法

[NOIP2004 普及组] FBI 树 题目描述: 我们可以把由 0 和 1 组成的字符串分为三类&#xff1a;全 0 串称为 B 串&#xff0c;全 1 串称为 I 串&#xff0c;既含 0 又含 1 的串则称为 F 串。 FBI 树是一种二叉树&#xff0c;它的结点类型也包括 F 结点&#xff0c;B 结点和 I …

RocketMQ实现一个简单的秒杀接口

预设场景&#xff1a; “秒杀”这一词多半出现在购物方面&#xff0c;但是又不单单只是购物&#xff0c;比如12306购票和学校抢课&#xff08;大学生的痛苦&#xff09;也可以看成一个秒杀。秒杀应该是一个“三高”&#xff0c;这个三高不是指高血脂&#xff0c;高血压和高血糖…