跳到主要内容位置

实现前端网页 WebRTC 视频通话以及换脸特效

张旭乾
软件工程师 / B站UP主

因为疫情的原因,线上视频会议软件异军突起,成为了在家办公的主要沟通渠道。而最近抖音上“蚂蚁呀嘿”恶搞换脸的小视频也突然火了起来,那我就想了想能不能在视频会议的时候换张脸活跃下气氛?在 Github 上一番搜寻之后发现还真有办法,有一个开源的 Python 人工智能换脸的库,那正好趁着这个机会研究一下前端 WebRTC 实现视频通话功能,外加换脸操作。先看一下效果吧:

因为有涉及到一点点的后台,所以项目分成了两部分,一个是用于存放前端代码的 frontend 项目和存放后端代码的 backend 项目:

项目根目录
|-- backend
|-- frontend

另外这个视频需要电脑上有摄像头,没有的话可以想办法把手机当作电脑的摄像头。

注意:本教程中的代码仅供展示 AI 换脸技术的应用,不可以用于获取其他人隐私或其他任何非法目的。

编写页面

因为是前端实现,首先肯定是编写页面。这个页面比较简单,就是一个视频组件、显示用户 ID 的文本、呼叫对方视频的输入框和按钮,视频组件默认显示自己的视频,当视频接通之后就会显示对方的视频,而自己的视频会缩小到右上角。

HTML

在 frontend 目录下新建 index.html、style.css 和 index.js 文件。首先看一下 HTML 结构,index.html 中主要的代码如下:

<!-- frontend/index.html -->
<head>
<!-- 其他代码 -->
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main>
<div id="container">
<div class="videos">
<video id="myVideo" class="videoSize" autoplay></video>
<video id="peerVideo" class="videoSize" autoplay></video>
</div>
<p id="idText"></p>
<div class="call">
<input type="text" id="peerIdInput" placeholder="请输入对方 id" />
<button id="joinBtn">视频通话</button>
</div>
</div>
</main>
<script src="index.js"></script>
</body>

每个标签的作用是:

  • <main />  用于设置页面背景,把所有组件居中对齐。
  • <div class="videos">  里边分别放了显示自己视频 #myVideo  和对方视频 #peerVideo  的 <video />  组件,并设置为自动播放,以便于在加载摄像头后立刻开始播放画面。
  • <p id="idText"> 在页面打开时显示自己的用户 ID,相当于是电话号码。
  • <div class="call">  里是输入对方 ID 的文本框 #peerIdInput  和呼叫按钮 #joinBtn 。
  • 最后在 <head />  中引入样式文件 style.css,在 <body />  结束前引入 index.js。我们将主要在 index.js 中编写代码。

CSS

css 的代码都比较简单,基本就是设置一下样式,这里介绍一下重要的部分,剩余的可以在源代码中查看。因为自己的视频要在视频接通时移动到右上角,那么就需要把 <div class="videos"> 容器设置为相对定位,把我的视频和对方的视频设置成一样的宽高,然后先隐藏对方视频,当视频接通时,利用 JavaScript 加上接通后,我的视频的样式,把我的视频设置为绝对定位,宽高调小,放到右上角:

/* frontend/style.css */
videos {
position: relative;
}

.videoSize {
width: 500px;
height: 600px;
object-fit: cover; /* 让视频按比例占满整个空间 */
}

.rightTop {
/* 以下是通话中的样式 */
position: absolute;
width: 150px;
height: 180px;
right: 0;
top: 0;
}

#peerVideo {
display: none;
}

其他组件基本只是设置了下 Grid 布局、宽高、大小、阴影、背景、字体,没有什么特殊的,可以直接查看源代码。样式中所用到的图标在 frontend/icons  目录下。

访问摄像头

接下来我们先熟悉一下访问摄像头的代码。在 JavaScript 中访问用户的摄像头主要使用 navigator.mediaDevices.getUserMedia() 方法,  它接收一个对象作为参数,用于指定要获取的设备,例如视频或音频,然后返回一个 Promise,在 Promise 完成之后它会传递给我们一个 Stream 流,我们把它放到 <video />  标签的 srcObject  属性中就可以了,是不是很简单?代码如下:

// frontend/index.js
const myVideo = document.getElementById("myVideo");

navigator.mediaDevices
.getUserMedia({ video: true, audio: true })
.then((stream) => {
myVideo.srcObject = stream;
});

在这段代码中:

  • 获取了 #myVideo  这个 <video />  组件。
  • 使用 navigator.mediaDevices.getUserMedia() 并给它传递了一个对象,对象的 video 和 audio 属性都设置为了 true,表示要访问摄像头和音频设备。

这时,使用 VS Code 的 Live Server 插件运行项目(没有的话安装一下,很简单),在 index.html 文件里右击,选择 Open with Live Server,打开之后,浏览器可能会提示此网站需要访问摄像头和音频设备,点击允许,就能看到自己的视频了。

编写后台

要实现视频通话,需要使用 WebRTC 技术,这个技术牵扯的概念和 API 过于庞大和复杂,不过有开源的库来帮我们简化了 WebRTC 的操作,这里使用一个叫做 Peer.js 的库,它封装了 WebRTC 杂乱的 API,提供了完整的、可配置的、易于使用的 API。 https://peerjs.com/ 我们将会通过把 Peer 附加到 Express.js 服务器上,来生成用户 ID 并管理 WebRTC 连接。首先在 backend 目录下运行:

npm init -y
# 或
yarn init -y

接着初始化一个 node.js 项目,使用 npm 或 yarn 安装 peer 和 express 依赖,因为想在改动代码时自动重启服务,我们也可以再安装一个 nodemon 依赖:

yarn add peer express nodemon
# 或
npm install --save peer express nodemon

安装完成之后新建一个 server.js 文件,整个后台服务我们就只需要这一个文件,都是一些简单的初始化代码,它里边的内容是:

const express = require("express");
const { ExpressPeerServer } = require("peer");

const app = express();

const server = app.listen(3000);

const peerServer = ExpressPeerServer(server, {
debug: true,
path: "/",
});

app.use("/video", peerServer);

这些代码的含义是:

  • 导入 express 库,并从 peer 库中导入与 express 进行结合的 ExpressPeerServer。
  • 创建 express 实例 app ,并监听 3000  端口。
  • 把 ExpressPeerServer 挂载到 express 中,设置 debug  开发模式为 true,这样有更好的错误提示。路径为根目录。
  • ExpressPeerServer 挂载之后会返回一个 express 的控制器。
  • 把返回的控制器挂载到 /video  路径下,这样 /video  路径就是主要的、peer.js 提供的 WebRTC 通信路径。

然后修改一下 package.json 文件,添加一个 script  配置项,里边定义 start  命令值为 nodemon server.js :

  "scripts": {
"start": "nodemon server.js"
},

这样使用 nodemon 运行 server.js 文件后,如果 server.js 中的内容发生变化,它会自动帮助我们重启服务器。 我们现在来运行一下 yarn start  或者 npm start ,看到命令行提示 [nodemon] starting node server.js  就算启动成功了,我们访问一下 http://localhost:3000/video  ,看到下方输出结果就说明 peer 也加载成功了:

{
"name": "PeerJS Server",
"description": "A server side element to broker connections between PeerJS clients.",
"website": "https://peerjs.com/"
}

后端代码到这里就编写完成了。下一步就是在前端页面中调用 Peer 相关的 api,并建立视频通话。

生成用户 ID

在前端中调用后端 Peer 服务可以使用 Peer.js 官方的前端库,可以直接使用 cdn 形式:

<script src="https://unpkg.com/peerjs@1.3.1/dist/peerjs.min.js"></script>

也可以打开上边 src 中的网址,把文件保存到本地,或者在 Github 上下载: https://github.com/peers/peerjs/blob/master/dist/peerjs.min.js 下载完成之后把它放到 frontend/peer  目录下,然后在 index.html 中,在引入 index.js 的上方,引入 peerjs:

<script src="peer/peerjs.min.js"></script>
<script src="index.js"></script>

接下来打开 index.js 文件,建立与后台 peer 服务的连接:

const peer = new Peer({
host: "localhost",
port: "3000",
path: "/video",
});

这里直接使用 peer.js 前端库导出的构造函数 Peer(),它接收一个对象作为参数,这里就分别把后台服务的 host、port 和 path 传递进去就可以了,它会返回 peer 实例,后续有关视频通话的操作就主要使用它来实现。 当成功的连接到后台服务之后,我们首先给自己生成一个唯一的用户 ID,就等同于是一个电话号码,那么这里我们可以监听 peer 的 open  事件,当连接打开后,会把生成的用户 ID 返回到事件处理回调函数中,然后我们获取 html 中的 #idText  这个 p 元素来显示自己的 ID:

const idText = document.getElementById("idText");

peer.on("open", (id) => {
idText.textContent = "我的 id 是:" + id;
});

这时在 Live Server 中打开 index.html,就可以看到显示出了 ID,类似于:

我的 id 是:2573c3ae-ba79-404a-b807-2128856ef3c9

多打开几个页面,可以看到每个人的 ID 都不同。

呼叫视频通话

在有了用户 ID 之后,就可以呼叫对方了。这里的逻辑是,用户在输入框输入对方 ID 之后,点击视频通话按钮进行呼叫。那么我们应该先获取视频通话按钮元素,然后监听它的点击事件,在里边发起呼叫:

const joinBtn = document.getElementById("joinBtn");
// 发起呼叫
joinBtn.addEventListener("click", () => {
const peerId = peerIdInput.value;
console.log("正在连接:" + peerId);

navigator.mediaDevices
.getUserMedia({ video: true, audio: true })
.then((stream) => {
const call = peer.call(peerId, stream);
call.on("stream", showVideo);
});
});

在点击按钮的时候,事件处理函数作了如下操作:

  • 获取用户输入的对方的 ID。
  • 获取当前用户的视频流,然后调用 peer.call()  呼叫对方, peer.call()  需要对方的 ID 和自己的视频流作为参数,然后返回与呼叫有关的实例,保存到 call 中。
  • 这时当前用户就开始等待对方应答了,为了简单起见,这里没有做等待的样式。
  • 下一步监听 call 的 stream  事件,这个事件会在对方应答后触发,它会返回对方的视频流作为事件处理函数的参数,然后我们使用 showVideo()  函数处理对方的视频流。

showVideo()  函数的代码如下:

const peerVideo = document.getElementById("peerVideo");

function showVideo(stream) {
myVideo.classList.add("rightTop");
peerVideo.srcObject = stream;
peerVideo.style.display = "block";
}

这个函数就是简单的把自己的视频移动到右上角,通过之前定义的 .rightTop class 样式,然后把对方的视频流放到 #peerVideo  视频组件中,之后把它显示出来(之前设置的是 display: none  隐藏)。现在因为一直是等待对方接听,所以需要有一个应答的处理。

应答视频通话

应答的处理是监听 peer 的 call  事件,然后通过事件参数中与呼叫有关的实例来应答通话:

// 应答呼叫
peer.on("call", (call) => {
navigator.mediaDevices
.getUserMedia({ video: true, audio: true })
.then((stream) => {
call.answer(stream);
call.on("stream", showVideo);
});
});

这一步的代码虽然在同一个文件中,但是应该想象为收到视频通话呼叫的对方,代码作了下边的操作:

  • 获取自己的视频流。
  • 调用 answer()  函数应答视频呼叫,并把自己的视频流传回给呼叫者。这里是当有呼叫时直接进行应答,为了简单起见,没有编写点击应答按钮相关的样式和事件。
  • 监听 ** stream **  事件,这一步和之前的一样,应答后这个事件就会触发,然后同样使用 showVideo() 函数加载视频。

好了,现在打开两个页面试试,在其中一个页面输入另一个页面生成的 ID,然后点击视频通话,就可以看到双方视频显示出来了。如果用的是同一个摄像头,那么显示的画面都是一样的。

实现换脸

实现换脸主要是用到了 Avatarify 这个 AI 换脸库,它是使用 Python 编写的,利用了 First-Order Motion Model 算法来实现换脸。我们这里只需要按照步骤进行安装就可以了,此外还需要安装虚拟摄像头软件,用于把 Avatarify 换脸后的数据作为摄像头视频,然后在前端页面中访问这个虚拟的摄像头。 Avatarify 由于是对摄像头的视频进行实时计算,所以对显卡的要求非常高,且只支持启用了 CUDA 的 Nvidia 显卡来进行 GPU 加速,官方的统计数据是这样的:

  • GeForce GTX 1080 Ti: 33 帧/秒
  • GeForce GTX 1070: 15 帧/秒
  • GeForce GTX 950: 9 帧/秒

如果配置达不到,可以使用腾讯云或阿里云的 GPU 实例,把显卡有关的计算交给远程服务器去进行,本地只需要建立视频流即可。本文将主要使用这种方法。 如果配置达到了要求,也可以在本地安装运行,不过我没有试过,后边简单的贴一下官方安装指导(Windows 下),你可以自己尝试一下。 要注意的是,如果是在本地运行算法,可以在 Windows 和 Linux 操作系统中进行,或者也可以使用 docker 进行部署,不过只支持 linux 操作系统下的 docker。

购买服务器

这里以腾讯云 GPU 实例为例,介绍一下服务器的配置,关于计费我们会使用按量计费,完成这篇教程所用的总体费用可能在 30 元左右, 用完之后一定要记得关机或者销毁来停止计费。如果是关机,记得先把带宽调整为 0,停止带宽计费,然后关机时选择关机不收费选项。 打开下方链接(或复制 https://curl.qcloud.com/KiCrCXo9 ):

腾讯云服务器购买链接

然后在选择机型中选择如下配置:

  • 计费模式:按量计费。
  • 地域:中国香港。这里选择香港地区的原因是安装 Avatarify 需要大量的境外源,例如 Github、Docker、Nividia、Python 等,且数量多,体积也大(有的 700 多 M),在亲身测试过国内区的之后,发现行不通,而香港地区因为延迟相对较低,且访问境外源速度快,所以这里选择了香港。
  • 实例:选择 GPU 型,在机型列表中选择 GPU 计算型 GN7,这个是最便宜的,7.16 元/小时。
  • 镜像:选择 Ubuntu 64 位 18.04,不要选过高的版本。显卡驱动可能支持不完善。
  • 勾选上后台自动安装 GPU 驱动,CUDA 版本选择 10.0.130,cuDNN 版本选择 7.4.2。
  • 带宽实测需要 20M 起步,大概 1 元/小时,不过在安装 Avatarify 的时候带宽可以小一点,后边真正开启视频的时候再把它改大。
  • 注意这里系统盘费用是 0.03 元/小时,这个在关机后仍然会收取

在下一步设置主机中:

  • 新建安全组。
  • 登录方式这里使用密码,可以选择自行设置,也可以自动生成。

再下一步确认成功后完成购买,稍后就可以在控制台实例列表中看到刚购买的实例了,新购买的机器可能需要等 5 分钟才能配置完成。如果要修改带宽,可以在列表中找到对应的实例,在最右侧操作栏里选择更多->资源调整->调整带宽,进行调整。 因为 Avatarify 需要使用 5557 和 5558 端口,我们需要在安全组中放行这两个端口。点击实例名称超链接,进入实例详情页面,在上方选项卡中选择 安全组 ,在右侧 规则预览  中的 入站规则  选项卡里,点击 编辑规则,在新页面里的入站规则里,点击 **添加规则,**在对话框里填写:

  • 类型:自定义。
  • 来源:all。
  • 协议端口:TCP:5557,5558
  • 策略:允许。

点击完成即可。到这里实例配置就完成了,留意一下实例的公网 IP 地址,稍后使用 SSH 登录时需要用到它。

部署 Avatarify

我们使用 Docker 的方式来部署 Avatarify,先登录到我们的 GPU 实例中,可以直接在腾讯云控制台登录,也可以使用 ssh 命令或 putty 工具。这里以 ssh 为例,输入如下命令:

ssh ubuntu@你的实例ip

然后根据提示输入密码,第一次登录可能有一大段英文提示(是否信任设备),直接输入 yes 回车即可。

安装 Docker

登录进去之后先安装 docker,可以直接使用简易的安装脚本:

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

不过实测香港的实例好像并不能访问这个脚本,我们还可以使用普通方式安装,按照以下步骤复制粘贴命令:

# 第一步
sudo apt-get update

# 第二步
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg

# 第三步
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

# 第四步
echo \
"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

这些步骤都是在配置 Docker 的安装仓库源和密钥,接下来运行下面两条命令来安装 Docker:

sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io

安装完成之后, docker  命令需要管理员权限才能运行,每次都需要输入 sudo,如果要省略 sudo ,可以把当前用户(腾讯云默认为 ubuntu)添加到 docker 用户组中:

sudo usermod -aG docker ubuntu

之后退出 ssh 并重新登录来让配置生效,我们可以运行下面的命令检查 docker 是否安装成功:

docker run hello-world

有打印出 hello world 字样和一大段英文就说明成功了。

安装 Nvidia Docker tookit

要让 docker 使用 GPU,需要安装 Nvidia Docker tookit,这一步也很简单,首先设置 Nvidia 的仓库源:

distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
&& curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - \
&& curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list

然后安装 tookit 并重启 Docker:

sudo apt-get update
sudo apt-get install -y nvidia-docker2
sudo systemctl restart docker

运行一个示例 container 检查是否安装成功:

sudo docker run --rm --gpus all nvidia/cuda:11.0-base nvidia-smi

如果打印出类似的如下信息就算成功了:

Fri Mar  5 08:47:39 2021
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 418.126.02 Driver Version: 418.126.02 CUDA Version: 11.0 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|===============================+======================+======================|
| 0 Tesla T4 Off | 00000000:00:08.0 Off | 0 |
| N/A 26C P8 9W / 70W | 0MiB / 15079MiB | 0% Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes: GPU Memory |
| GPU PID Type Process name Usage |
|=============================================================================|
| No running processes found |
+-----------------------------------------------------------------------------+

构建 Avatarify Docker 镜像

选择一个文件夹,使用 git 克隆 Avatarify 仓库:

git clone https://github.com/alievk/avatarify.git

进入到克隆下来的 avatarify 仓库中,使用 docker 构建镜像:

cd avatarify
docker build -t avatarify .

构建需要的时间比较长,耐心等待完成之后,启动服务:

bash run.sh --is-worker --docker

看到有 5557 和 5558 端口就说明启动成功了。到现在,Avatarify 的服务端配置完成了,之后就是使用客户端调用服务,把计算部分交给我们的 GPU 实例。这时如果担心计费,可以先把实例关机,在本地安装好客户端后再开机。 接下来的步骤以 MacOS 为例,Windows 版可以直接跳到后边的 Windows 安装方法  小节。

安装 Avatarify 客户端

在本地电脑上,首先安装 Miniconda Python 3.8,可以从这里下载: https://docs.conda.io/en/latest/miniconda.html#macosx-installers 或者使用 Homebrew:

brew install --cask miniconda

克隆 avatarify 仓库并运行安装脚本:

git clone https://github.com/alievk/avatarify.git
cd avatarify
bash scripts/install_mac.sh

下载 CamTwist 虚拟摄像头软件(类似 OBS,但是 OBS 的虚拟摄像头在 Mac 中访问不到)并安装: http://camtwiststudio.com/download

启动 Avatarify 客户端

确保远程 GPU 实例已启动,且运行了 avatarify docker 镜像监听 5557 和 5558 端口,然后在本地克隆的 avatarify 仓库中,运行 mac 客户端:

./run_mac.sh --is-client --in-addr tcp://server_address:5557 --out-addr tcp://server_address:5558

记得把两个 server_address  改成 GPU 实例的公网 IP。服务端可能需要下载一些必要的文件,所以连接会慢一些,稍等启动成功后 Avatarify 会自动打开摄像头窗口。 我们可以根据提示调整好脸部位置,这里我们最好把脸放到红色矩形框中,光线要充足,不能太黑,可以使用 W/D 键缩放画面,调整好之后按 X 键就可以呼出换脸之后的预览视频窗口,它就是作为虚拟摄像头的视频来源。 Avatarify 内置了 9 张示例的人脸照片,可以按 1-9 键进行切换,也可以自定义人脸照片,放到 Avatarify 中的 avatars  文件夹下。 Avatarify 成功显示之后,打开 CamTwist 虚拟摄像头,在左侧选择 Desktop+,然后点击底部的 select,然后在右侧的 settings 下,勾上 Confine to Application Window,然后勾上下方的 Select from existing windows ,在下拉框中选择 python (avatarify) ,如果找不到可以退出 CamTwist 重新打开,或者多点击下拉框几次试试。现在虚拟摄像头就准备好了。

Windows 安装方法

在 Windows 下的安装方法与 MacOS 类似,而且如果显卡配置够高,可以省略购买远程 GPU 服务器步骤。

  1. 首先安装 Minicoda Python 3.8,可以从这里下载:

https://docs.conda.io/en/latest/miniconda.html#windows-installers

  1. 安装完成之后打开 Anaconda 命令提示符,克隆 avatarify 仓库,并运行 windows 操作系统下的安装脚本:
git clone https://github.com/alievk/avatarify.git
cd avatarify
scripts\install_windows.bat
  1. 如果要在本地执行换脸算法,那么需要下载 network weights(228 MB):

https://openavatarify.s3.amazonaws.com/weights/vox-adv-cpk.pth.tarhttps://yadi.sk/d/M0FWpz2ExBfgAAhttps://drive.google.com/file/d/1coUCdyRXDbpWnEkA99NLNY60mb9dQ_n3/view?usp=sharing

  1. 下载完成后放到 avatarify 的根目录下,无需解压。
  2. 运行 avatarify 客户端,如果是在本地运行,可以使用:
./scripts/run_windows.bat
  1. Windows 系统下也可以使用远程 GPU 实例,使用下面的方式启动客户端:
./scripts/run_windows.bat --is-client --in-addr tcp://server_address:5557 --out-addr tcp://server_address:5558
  1. 两个 server_address  需要替换成远程 GPU 实例的公网 IP。
  2. 启动成功之后和 Mac 的操作一样,使用 W/D 缩放画面,完成之后按 X 键调出 avatarify 预览窗口。

Windows 下的虚拟摄像头可以使用 OBS,最新版的 OBS 26.1 及以上已经内置了虚拟摄像头插件了,无需再单独安装。

  1. 下载并安装 OBS:

https://obsproject.com/

  1. 安装完成之后打开 OBS,在左下角的 Source(源)面板中添加 Windows Capture,把视频输入源设置为某个应用程序窗口,然后选择 [python.exe]: avatarify 程序,点击 OK,可以调整以下大小。
  2. 再在 Tools(工具)菜单中选择 VirtualCam(虚拟摄像头),选择 AutoStart(自动开启),设置 Buffered Frames(缓冲)为 0,然点击开始。(如果 Tools 菜单没有 VirtualCam,看看界面右下角的 start recording 附近有没有 start virtual cam)。

现在虚拟摄像头就准备好了。

实现换脸

接下来就是在我们的视频通话前端项目中,使用 CamTwist 或 OBS 提供的虚拟摄像头了。我们需要明确知道它们的设备 ID 才能指定具体使用哪个摄像头。 首先使用 Live Server 打开前端项目的 index.html 文件,在谷歌开发者工具的 console 中,使用下面这段代码打印出视频设备信息:

navigator.mediaDevices.enumerateDevices().then(function (devices) {
devices.forEach(function (device) {
if (device.kind === "videoinput") {
console.log(device);
}
});
});

这段代码就是访问所有媒体设备,然后获取其中的视频设备并打印出来,从打印结果里找到 label 包含 CamTwist 或者 OBS(视操作系统而定)的那一项,记录 deviceId 属性。 在 index.js 文件中,保存 deviceId 值到一个常量中:

const cameraId =
"982947417cf490bae44ffb6a837bddcb813704ee491dd85d9149c45389f5521b";

在使用 navigator.mediaDevices.getUserMedia()  获取摄像头时,除了可以把 video 设置为 true 之外,还可以把他的值设置为一个对象,用来指定更多的信息,我们把它单独定义出来,在里边使用 cameraId 指定访问哪个摄像头:

const mediaConstrains = {
video: {
deviceId: {
exact: cameraId,
},
},
audio: true,
};

exact 是精确的、只使用指定 ID 的摄像头,不会使用其他备选的。 然后在获取自己摄像头设备的部分,替换成上边这个对象。或者为了方便测试,也可以把所有的都替换掉:

navigator.mediaDevices.getUserMedia(mediaConstrains).then(/* ... */);

好了,现在再试试视频通话,是不是成功的换脸了呢? 这里要注意的是,可能是因为 CamTwist 软件和真正的摄像头有冲突,页面会不时的重新刷新,这个暂时还没有比较好的解决方案,如果你有精力的话可以研究一下。 附上本文的源代码: https://github.com/zxuqian/code-examples/tree/master/webrtc/video-call-change-face

总结

这篇教程所用到的技术都很简单,只是配置比较复杂,本教程提供了换脸的基本步骤,剩下的可以根据需要继续进行完善。示例中实现的的是一对一的视频通话,不过在这个基础上也完全可以实现多方视频会议,只需要调整一下视频显示的样式,然后在有新人加进来时,创建相应的 <video />  标签,并追加到现有的视频列表中即可。现在我们来回顾一下过程:

  • 编写前端 HTML/CSS 页面结构和样式,并尝试加载摄像头。
  • 编写简单的后台应用,利用 epxress 和 peer 建立基于 WebRTC 的视频通话基础。
  • 前端利用 peer.js 前端库调用后台生成用户 ID,并呼叫、应答视频通话。
  • 配置 Avatarify 换脸服务器程序,利用云 GPU 服务把显卡计算相关部分转移到 GPU 实例中。
  • 安装 Avatarify 客户端程序和虚拟摄像头应用,把换脸后的数据用虚拟摄像头展现出来。
  • 前端获取虚拟摄像头 ID,在视频通话的时候使用此摄像头。

如果有帮助请点赞并分享,有问题可以评论留言,感谢阅读!

提示

一系列的课程让你成为高级前端工程师。课程覆盖工作中所有常用的知识点和背后的使用逻辑,示例全部都为工作项目简化而来,学完即可直接上手开发!

即使你已经是高级前端工程师,在课程里也可能会发现新的知识点和技巧,让你的工作更加轻松!

《React 完全指南》课程,包含 React、React Router 和 Redux 详细介绍,所有示例改编自真实工作代码。点击查看详情。

《Vue 3.x 全家桶完全指南与实战》课程,包括 Vue 3.x、TypeScript、Vue Router 4.x、Vuex 4.x 所有初级到高级的语法特性详解,让你完全胜任 Vue 前端开发的工作。点击查看详情。

《React即时通信UI实战》课程,利用 Storybook、Styled-components、React-Spring 打造属于自己的组件库。

《JavaScript 基础语法详解》本人所著图书,包含 JavaScript 全面的语法知识和新特性, 可在京东、当当、淘宝等各大电商购买