WebRtc实现1V1音视频通话

WebRtc实现1V1音视频通话

简介

WebRTC,名称源自网页实时通信(Web Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音通话或视频聊天的技术,是谷歌 2010 年以 6820 万美元收购 Global IP Solutions 公司而获得的一项技术。

WebRTC 提供了实时音视频的核心技术,包括音视频的采集、编解码、网络传输、显示等功能,并且还支持跨平台:windows,linux,mac,android。
官方文档
官方中文文档
相关中文文档

应用场景

实时视频通话:WebRTC 可用于实现浏览器之间的实时视频通话,无论是一对一通话还是多方通话。这对于视频会议、在线教育和远程医疗等场景非常有用。

音频通话:除了视频通话,WebRTC 也支持浏览器之间的实时音频通话。这对于实现 VoIP(Voice over Internet Protocol)应用程序非常有用,如网络电话、语音聊天等。

屏幕共享:WebRTC 允许用户在浏览器之间共享屏幕,这在远程协作、在线培训和技术支持等场景下非常实用。

文件传输:WebRTC 不仅可以传输实时音视频数据,还可以用于浏览器之间的实时数据传输,包括文件传输。这对于实现点对点的文件共享或传输非常方便。

网络游戏:WebRTC 可以用于实现基于浏览器的实时多人游戏,因为它提供了低延迟和高质量的实时通信。

物联网(IoT):WebRTC 的轻量级特性使其适用于 IoT 设备之间的实时通信,如智能家居设备、监控摄像头等。

虚拟现实(VR)和增强现实(AR):WebRTC 可以用于在浏览器中实现 VR 和 AR 应用程序中的实时音视频通信,如多人虚拟会议、共享虚拟体验等。

共享桌面的基本原理

传统共享桌面

共享桌面的基本原理其实非常简单:

对于共享者,每秒钟抓取多次屏幕(可以是 3 次、5 次等),每次抓取的屏幕都与上一次抓取的屏幕做比较,取它们的差值,然后对差值进行压缩;如果是第一次抓屏或切幕的情况,即本次抓取的屏幕与上一次抓取屏幕的变化率超过 80%时,就做全屏的帧内压缩,其过程与 JPEG 图像压缩类似。最后再将压缩后的数据通过传输模块传送到观看端﹔数据到达观看端后,再进行解码,这样即可还原出整幅图片并显示出来。对于远程控制端,当用户通过鼠标点击共享桌面的某个位置时,会首先计算出鼠标实际点击的位置,然后将其作为参数,通过信令发送给共享端。共享端收到信令后,会模拟本地鼠标,即调用相关的 API,完成最终的操作。一般情况下,当操作完成后,共享端桌面也发生了一些变化。通过上面的描述,可以总结出共享桌面的处理过程为︰抓屏、压缩编码、传输、解码、显示、控制这几步。对于共享桌面,很多人比较熟悉的可能是 RDP ( Remote Desktop Protocal)协议,它是 Windows 系统下的共享桌面协议﹔还有一种更通用的远程桌面控制协议------VNC ( Virtual Network Console ),它可以实现在不同的操作系统上共享远程桌面,像 TeamViewer、RealVNC 都是使用的该协议。其实在 WebRTC 中也可以实现共享远程桌面的功能。但 WebRTC 的远程桌面不需要远程控制,所以其处理过程使用了视频的方式,而非传统意义上的RDP/VNC 等远程桌面协议,仍然属于音视频采集的范畴,但是这次采集的不是音视频数据而是桌面。

WebRTC 共享桌面

WebRTC 在桌面数据处理的有好几个环节:

第一个环节,共享端桌面数据的采集。WebRTC 对于桌面的采集与RDP/VNC 使用的技术是相同的,都是利用各平台所提供的相关 API 进行桌面的抓取。以 Windows 为例,可以使用下列 API 进行桌面的抓取。BitBlt : XP 系统下经常使用,在 vista 之后,开启 DWM 模式后,速度极慢。Hook :一种黑客技术,实现稍复杂。DirectX:由于 DirectX 9/10/11 之间差别比较大,容易出现兼容问题。最新的WebRTC 都是使用的这种方式GetWindowDC:可以通过它来抓取窗口。第二个环节,共享端桌面数据的编码。WebRTC 对桌面的编码使用的是视频编码技术,即 H264/VP8 等;但 RDP/VNC 则不一样,它们使用的是图像压缩技术。使用视频编码技术的好处是压缩率高,而坏处是在网络不好的情况下会有模糊等问题。第三个环节,传输。编码后的桌面数据会通过流媒体传输协议发送到观看端。对于 WebRTC 来说,当网络有问题时,数据是可以丢失的。但对于RDP/VNC 来说,桌面数据一定不能丢失。第四个环节,观看端解码。WebRTC 对收到的桌面数据通过视频解码技术解码,而RDP/VNC 使用的是图像解码技术(可对比第二个环节)。第五个环节,观看端渲染。一般会通过 OpenGL/D3D 等 GPU 进行渲染,这个 WebRTC 与 RDP/VNC 都是类似的。

其本质就是将采集源,从摄像头换成了屏幕:

javascript 复制代码
//视屏通话参数,设置采集源为前置摄像头
var mediaOpts = {
audio: true,//是否采集音频
video: {
	facingMode: "user",//调用前置摄像头
	}
}
navigator.mediaDevices.getUserMedia(mediaOpts)
javascript 复制代码
//视屏通话参数,设置采集源为当前屏幕
 var mediaOpts = {
        audio: true,  //是否采集音频
        video: {
            mediaSource: 'screen'  // 设置采集源为屏幕
        }
    };

navigator.mediaDevices.getUserMedia(mediaOpts)

相关API

getUserMedia() API,提示用户许可使用其网络摄像头或其他视频或音频输入(获取摄像头和麦克风的权限),通过指定一组(强制或可选)成功和失败的回调函数,可以访问本地设备媒体(音频或视频)

javascript 复制代码
navigator.mediaDevices.getUserMedia(constraints, successCallback, errorCallback)

它返回一个 JS 中的 Promise 对象。如果 getUserMedia 调用成功,则可以通过 Promise 获得 MediaStream 对象,也就是说现在我们已经从音视频设备中获取到音视频数据了。

如果调用失败,比如用户拒绝该 API 访问媒体设备(音频设备、视频设备),或者要访问的媒体设备不可用,则返回的 Promise 会得到PermissionDeniedError 或 NotFoundError 等错误对象
参数 constraints,其类型为MediaStreamConstraints。它可以指定 MediaStream 中包含哪些类型的媒体轨(音频轨、视频轨),并且可为这些媒体轨设置一些限制
例:

javascript 复制代码
// 该结构可以指定采集音频还是视频,或是同时对两者进行采集,比如:
const mediaStreamContrains = {
video: true,
audio: true
};

并且通过该结构,还能做进一步的设定

javascript 复制代码
var constraints= {
	video :{
		width: 640,//宽度是 640
		height:480,//高度是 480
		frameRate:15,//视频的帧率 15 帧每秒
		facingMode:'enviroment'//使用后置摄像头,可选的值:user(前置摄像头)、environment(后置摄像头)、left(前置左侧摄像头)、right(前置右置摄像头)
	},
	audio : false//音频未开启
}

音视频的设定远不止这些,比如音频还可以设置为开启回音消除、降噪以及自动增益功能,视频还可以设置是否启用裁剪、所选摄像头等等。更详细的设置可以参考WebRTC 规范

createObjectUrl() 方法指示浏览器创建和管理与本地文件或二进制对象(blob)关联的唯一URL,它在 WebRTC 中的典型用法是从 MediaStream 对象开始创建 Blob URL 。 然后,将在 HTML 页面内使用 Blob URL

javascript 复制代码
createObjectURL(stream)

MediaDevices:

该接口提供了访问(连接到计算机上的)媒体设备(如摄像头、麦克风)以及截取屏幕的方法。实际上,它允许你访问任何硬件媒体设备。
MediaDeviceInfo:

用于表示每个媒体输入/输出设备的信息,包含以下 4 个属性:
deviceId : 设备的唯一标识;
groupId : 如果两个设备属于同一物理设备,则它们具有相同的组标识符 - 例如同时具有内置摄像头和麦克风的显示器;
label : 返回描述该设备的字符串,即设备名称(例如"外部 USB 网络摄像头");
kind : 设备种类,可用于识别出是音频设备还是视频设备,是输入设备还是输出设备:audioinput/audiooutput/videoinput

MediaStream:

用于表示媒体数据流。 流可以是输入或输出,也可以是本地或远程(例如,本地网络摄像头或远程连接)
RTCPeerConnection:

建立点对点连接的关键,提供了创建,保持,监控,关闭连接的方法的实现。像媒体协商、收集候选地址都需要它来完成;调用 new RTCPeerConnection(configuration) 将创建一个 RTCPeerConnection 对象。该配置具有查找和访问 STUN 和 TURN 服务器的信息(每种类型可以有多个服务器,任何 TURN 服务器也可以用作 STUN 服务器)
STUN 服务器(Session Traversal Utilities for NAT):

  • STUN服务器用于发现客户端位于NAT后面的真实IP地址和端口。
  • 客户端在与STUN服务器进行通信时,可以获取自己的公网IP地址和端口。
  • STUN服务器不对数据流进行修改,它只是用于获取NAT后端的地址信息。
  • STUN服务器通常用于建立点对点连接,如WebRTC应用程序,其中客户端需要了解其真实的网络地址以便直接通信。

TURN 服务器(Traversal Using Relays around NAT):

  • TURN服务器用于在两个客户端无法直接通信时,作为中间转发点传输数据。其本质就是一个收发socket消息的服务器
  • 当两个客户端无法建立直接连接时(例如由于防火墙或双方均位于对称NAT后面),它们可以通过TURN服务器进行通信。
  • TURN服务器会接收客户端的数据,并将其转发给目标客户端,从而绕过了无法直接连接的障碍。
  • 虽然TURN服务器提供了中继服务,但是由于数据都要通过服务器中转,因此会增加数据传输的延迟和服务器的负载。

createOffer:

createOffer() 方法生成一个 SDP Blob,其中包含:

具有会话支持的配置 RFC3264 的 offer,

附加(attached)的 localMediaStreams 的描述,

浏览器支持的 codec/RTP/RTCP 选项,

ICE 收集的所有候选对象,

可以提供约束参数以对生成的要约提供附加控制

createAnswer:

createAnswer() 方法用于创建应答SDP

close:

close() 方法销毁 RTCPeerConnection ICE 代理,结束任何活动的 ICE 处理和任何活动的流,并释放任何相关资源

更多API介绍

基本使用

调用本地摄像头

1.创建如下目录

2.编写界面

html 复制代码
<!DOCTYPE html>
<html>
  <head>
    <title>测试摄像头调用</title>
  </head>
  <body>
    <div id="mainDiv">
      <p>展示当前摄像头捕捉画面</p>
      <video autoplay></video>
      <script src="./js/getUserMedia.js"></script>
    </div>
  </body>
</html>

3.编写JS

javascript 复制代码
//不同的浏览器需要使用不同的方式调用
// API method:
// Opera --> getUserMedia
// Chrome --> webkitGetUserMedia
// Firefox --> mozGetUserMedia

//获取用户摄像头权限
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
// 指定采集视频,不采集音频
var constraints = {audio: false, video: true};
//获取video标签
var video = document.querySelector("video");
// 获取成功后执行的回调函数
function successCallback(stream) {

  window.stream = stream;
  if (window.URL) {
	  
   //将获取到的流展示在页面上,这种方式适用于谷歌浏览器
   video.srcObject = stream;
  } else {
    // 适用于火狐和Opera浏览器
    video.src = window.URL.createObjectURL(stream);
  }
  //播放视频
  video.play();
}
// 获取失败后执行的回调函数
function errorCallback(error) {
  console.log("navigator.getUserMedia error: ", error);
}
// 发起调用
navigator.getUserMedia(constraints, successCallback, errorCallback);

4.效果展示

火狐浏览器

谷歌

播放约束设置

1.编写界面

html 复制代码
<!DOCTYPE html>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <title>播放约束测试</title>
    </head>
    <body>
        <div id="mainDiv">
           
            <p>播放约束测试</p>
            <div id="buttons">
                <button id="qvga">320x240</button>
                <button id="vga">640x480</button>
                <button id="hd">1280x960</button>
            </div>
            <p id="dimensions"></p>
            <video autoplay></video>
            <script src="js/getUserMedia_constraints.js"></script>
        </div>
    </body>
</html>

2.编写JS

javascript 复制代码
//获取页面的3个按钮
var vgaButton = document.querySelector("button#vga");
var qvgaButton = document.querySelector("button#qvga");
var hdButton = document.querySelector("button#hd");
// 获取视屏显示区域
video = document.querySelector("video");
//采集到的视频流
var stream;
//兼容不同浏览器调用摄像头
navigator.getUserMedia = navigator.getUserMedia ||
  navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
// 调用成功后回调函数
function successCallback(gotStream) {
  //设置流
  window.stream = gotStream;

   //将获取到的流展示在页面上
   video.srcObject = stream;
  //播放视屏画面
  video.play();
}
// 获取摄像头失败回调函数
function errorCallback(error){
  console.log("navigator.getUserMedia error: ", error);
}
// 低分辨率约束
var qvgaConstraints = {
  video: {
    mandatory: {
      maxWidth: 320,
      maxHeight: 240
    }
  }
};
// 标准分辨率视频的约束对象
var vgaConstraints = {
  video: {
    mandatory: {
      maxWidth: 640,
      maxHeight: 480
    }
  }
};
// 高分辨率视频的约束对象
var hdConstraints = {
  video: {
    mandatory: {
      minWidth: 1280,
      minHeight: 960
    }
  }
};
// 点击事件
qvgaButton.onclick = function() {
  getMedia(qvgaConstraints)
};
vgaButton.onclick = function() {
  getMedia(vgaConstraints)
};
hdButton.onclick = function() {
  getMedia(hdConstraints)
};

//设置约束参数
function getMedia(constraints) {
  if (!!stream) {
    video.src = null;
    stream.stop();
  }
  navigator.getUserMedia(constraints, successCallback, errorCallback);
}

3.效果展示

媒体协商过程

一对一通信中,发起方发送的 SDP 称为Offer(提议)接收方发送的 SDP 称为Answer(应答)

每端保持两个描述:描述本身的本地描述LocalDescription,描述呼叫的远端的远程描述RemoteDescription。

当通信双方 RTCPeerConnection 对象创建完成后,就可以进行媒体协商了,大致过程如下:

1.发起方创建 Offer 类型的 SDP,保存为本地描述后再通过信令服务器发送到对端;

2.接收方接收到 Offer 类型的 SDP,将 Offer 保存为远程描述;

3.接收方创建 Answer 类型的 SDP,保存为本地描述,再通过信令服务器发送到发起方,此时接收方已知道连接双方的配置;

4.发起方接收到 Answer 类型的 SDP 后保存到远程描述,此时发起方也已知道连接双方的配置;

5.整个媒体协商过程处理完毕。

协议

RTP 协议和 RTCP 协议:

网络层的协议常见的有 TCP 和 UDP,对于音视频通信使用的则是 UDP。因为 TCP 协议在实现上为了保证传输的可靠,使用了滑动窗口、重发等机制,这其实对音视频通信其实是不利的。

因为音视频通信要求时延小,反而对数据的正确性要求没有那么高,人类的生理已经决定了通信过程中丢失一两帧的数据对用户的观看和收听没有太大影响,而且通过音视频算法可以做到很大程度的弥补。所以往往音视频通信都是基于 UDP 来实现的,不过不能直接把音视频数据流交给 UDP 传输,而是先给音视频数据加个 RTP 头,然后再交给 UDP 进行传输。当然,UDP 作为一种不可靠的传输,会发生丢包等情况,WebRTC 对这些问题在底层都有相应的处理策略。

什么是 RTP 头 ?一个视频帧的数据量是非常大的,最少也要几十K。而以太网的最大传输单元是 1500 字节,所以要传输一个帧需要拆成几十个包。并且这几十个包传到对端后,还要重新组装,这样才能进行解码还原出一幅幅的图像。要完成这样的过程,至少需要以下几个标识。
序号∶用于标识传输包的序号,这样就可以知道这个包是第几个分片了。
起始标记︰记录分帧的第一个 UDP 包。
结束标记∶记录分帧的最后一个 UDP 包。

有了上面这几个标识字段,就可以在发送端进行拆包,在接收端将视频帧重新再组装起来了。

RTP 协议就是做这个事的,其中有好几个字段,比如:
sequence number:序号,用于记录包的顺序。
timestamp:时间戳,同一个帧的不同分片的时间戳是相同的。这样就省去了前面所讲的起始标记和结束标记。
PT:Payload Type,数据的负载类型。音频流的 PT 值与视频的 PT 值是不同的,通过它就可以知道这个包存放的是什么类型的数据。

除此之外,WebRTC 还使用了 RTCP 协议让通信的两端知道它们自己的网络质量
RTCP 有两个最重要的报文:RR(Reciever Report)和 SR(Sender Report)。

通过这两个报文的交换,各端就知道自己的网络质量到底如何了。比如 SR 报文并不仅是指发送方发了多少数据,它还报告了作为接收方,它接收到的数据的情况。当发送端收到对端的接收报告时,它就可以根据接收报告来评估它与对端之间的网络质量了,随后再根据网络质量做传输策略的调整。

SDP 协议:

音视频通话时,沟通的双方必须要确保两方对音视频的数据理解是一致的,才能高效的沟通,比如 A 的音频数据采样率是 48000,使用双声道,而 B 只能支持音频采样频率是 32000,使用单声道,很明显进行通信的时候两者的音频通信使用 B 这边的格式就是最好的选择。

WebRTC 使用了 SDP(Session DescriptionProtocal)描述的各端(PC 端、Mac 端、Android 端、iOS 端等)的能力。这里的能力指的是各端所支持的音频编解码器是什么,这些编解码器设定的参数是什么,使用的传输协议是什么,以及包括的音视频媒体是什么等等。
两个客户端 / 浏览器进行 1 对 1 通话时,首先要进行信令交互,而交互的一个重要信息就是 SDP 的交换。

交换 SDP 的目的是为了让对方知道彼此具有哪些能力,然后根据双方各自的能力进行协商,协商出大家认可的音视频编解码器、编解码器相关的参数如音频通道数,采样率等、传输协议等信息。

举个例子,A 与 B 进行通讯,它们先各自在 SDP 中记录自己支持的音频参数、视频参数、传输协议等信息,然后再将自己的 SDP 信息通过信令服务器发送给对方。当一方收到对端传来的 SDP 信息后,它会将接收到的 SDP 与自己的SDP 进行比较,并取出它们之间的交集,这个交集就是它们协商的结果,也就是它们最终使用的音视频参数及传输协议了。

SDP 是文本格式的报文,WebRTC 对标准 SDP 规范做了一些调整,更具体的描述可以参考 SDP 规范

协议的交换与传输

对于通过浏览器进行 1 对 1 通话的整个流程中还需要第三方介入,方便双方进行正式交流之前的信息协商(传递协议信息),这个第三方被称为信令服务。

首先,通信双方将它们各自的媒体信息,如编解码器、媒体流参数、传输协议、IP 地址和端口等,按 SDP 格式整理好。然后,通信双方通过信令服务器交换 SDP 信息,并待彼此拿到对方的 SDP 信息后,找出它们共同支持的媒体能力。

最后,双方按照协商好的媒体能力建立连接开始音视频通信。

以上过程如图所示:

WebRTC 通信过程

WebRTC 实现一对一通信需要满足的基本条件:

WebRTC 终端(两个):本地和远端,负责音视频采集、编解码、NAT 穿越以及音视频数据传输等;
信令服务器:自行实现的信令服务,负责信令处理,如加入房间、离开房间、媒体协商消息的传递等;
STUN/TURN 服务器:负责获取 WebRTC 终端在公网的 IP 地址,以及 NAT 穿越失败后的数据中转服务。

通信过程如下:

  1. 本地(WebRTC 终端)启动后,检测设备可用性,如果可用后开始进行音视频采集工作;
  2. 本地就绪后,发送"加入房间"信令到 信令服务器;
  3. 信令服务器创建房间,等待加入;
  4. 对端(WebRTC 终端)同样操作,加入房间,并通知另一端;
  5. 双端创建媒体连接对象RTCPeerConnection,进行媒体协商;
  6. 双端进行连通性测试,最终建立连接;
  7. 将采集到的音视频数据通过RTCPeerConnection对象进行编码,最终通过 P2P 传送给对端/本地,再进行解码、展示。

ICE Candidate(ICE 候选者)

ICE 候选者表示 WebRTC 与远端通信时使用的协议、IP 地址和端口,结构如下:

javascript 复制代码
{
  address: xxx.xxx.xxx.xxx, // 本地IP地址
  port: number, // 本地端口号
  type: 'host/srflx/relay', // 候选者类型
  priority: number, // 优先级
  protocol: 'udp/tcp', // 传输协议
  usernameFragment: string // 访问服务的用户名
  ...
}

WebRTC 在进行连接测试后时,通信双端会提供众多候选者,然后按照优先级进行连通性测试,测试成功就会建立连接。

候选者 Candidate 类型,即 type 分为三种类型:
host:本机候选者,就是本机的 ip 地址 和端口

优先级最高,host 类型之间的连通性测试就是内网之间的连通性测试,P2P。

srflx:内网主机映射的外网地址和端口,使用STUN 协议获取

如果 host 无法建立连接,则选择 srflx 连接,即 P2P 连接。

relay:中继候选者,使用TURN 协议获取

优先级最低,只有上述两种不存在时,才会走中继服务器的模式,因为会增加传输时间,优先级最低

1V1视频通话

  • 通话过程
  • 实现效果
  • 代码地址
    gitee
相关推荐
世间万物皆对象6 小时前
Spring Boot核心概念:日志管理
java·spring boot·单元测试
qq_17448285757 小时前
springboot基于微信小程序的旧衣回收系统的设计与实现
spring boot·后端·微信小程序
代码小鑫9 小时前
A043-基于Spring Boot的秒杀系统设计与实现
java·开发语言·数据库·spring boot·后端·spring·毕业设计
真心喜欢你吖9 小时前
SpringBoot与MongoDB深度整合及应用案例
java·spring boot·后端·mongodb·spring
周全全10 小时前
Spring Boot + Vue 基于 RSA 的用户身份认证加密机制实现
java·vue.js·spring boot·安全·php
飞升不如收破烂~11 小时前
Spring boot常用注解和作用
java·spring boot·后端
计算机毕设源码qq-383653104111 小时前
(附项目源码)Java开发语言,215 springboot 大学生爱心互助代购网站,计算机毕设程序开发+文案(LW+PPT)
java·开发语言·spring boot·mysql·课程设计
岁岁岁平安11 小时前
springboot实战(15)(注解@JsonFormat(pattern=“?“)、@JsonIgnore)
java·spring boot·后端·idea
潜洋15 小时前
Spring Boot教程之五:在 IntelliJ IDEA 中运行第一个 Spring Boot 应用程序
java·spring boot·后端
灯雾️16 小时前
Spring Boot、Spring MVC和Spring间的区别
spring boot