26、项目实战:视频会议系统(五):性能优化(Simulcast、带宽自适应)、部署与测试(Docker + HTTPS)
好,咱们继续往下走。前面几章我们把视频会议的核心功能都搭起来了——信令、媒体协商、P2P连接,基本能跑了。但说实话,那只是“能跑”,离“好用”还差得远。
你想想看,一个视频会议,如果网络一波动就卡成PPT,或者参会者多了CPU直接拉满,这产品谁敢用?我当年做第一个版本的时候,就吃过这个亏。内测时大家坐同一层楼,效果还行。一放到公网,有人用4G,有人用WiFi,画面直接崩了。
所以这一章,咱们专门解决性能问题。核心就两招:Simulcast 和 带宽自适应。最后再聊聊怎么用Docker打包部署,配上HTTPS,让产品真正能上线。
Simulcast:让不同的人看到不同的画质
先问一个问题:一个会议室里,有人用27寸大屏,有人用手机,还有人网络信号只有两格。你该给所有人推同样的视频流吗?
显然不行。给手机推1080p,浪费带宽还发热。给信号差的人推高清,他根本收不到,全是花屏。
Simulcast 的思路很简单:发送端同时编码多个分辨率的视频流(比如720p、360p、180p),接收端根据自己的屏幕和网络,选最合适的那一路。
我习惯把Simulcast比作“自助餐”。厨师(发送端)多做几道菜,客人(接收端)自己挑。而不是厨师只做一道菜,所有人硬吃。
Simulcast 的配置要点
在WebRTC里开启Simulcast,主要是在 RTCRtpSender 的 setParameters 里做文章。但更常见的做法,是在创建 RTCPeerConnection 时,通过 addTransceiver 指定。
const pc = new RTCPeerConnection(config);
// 开启 Simulcast:发送三个不同分辨率的流
const transceiver = pc.addTransceiver('video', {
direction: 'sendrecv',
sendEncodings: [
{ rid: 'high', maxBitrate: 900000, scaleResolutionDownBy: 1.0 },
{ rid: 'mid', maxBitrate: 300000, scaleResolutionDownBy: 2.0 },
{ rid: 'low', maxBitrate: 100000, scaleResolutionDownBy: 4.0 }
]
});
这里有几个关键参数:
- rid:流的标识,接收端通过它来切换。
- maxBitrate:每个流的最大码率。高流给900kbps,低流给100kbps。
- scaleResolutionDownBy:分辨率缩放比例。1.0是原始分辨率,2.0是缩小一半。
接收端如何选择流
接收端不需要做太多事。浏览器底层会根据网络状况自动切换。但如果你想手动控制,可以监听 oniceconnectionstatechange 或使用 RTCRtpReceiver 的 getParameters 来查看当前收到的流。
// 接收端:查看当前收到的流信息
const receiver = pc.getReceivers()[0];
const params = receiver.getParameters();
console.log('当前接收的流:', params.encodings);
嗯,这里要注意:不是所有浏览器都完美支持Simulcast。Chrome和Firefox没问题,Safari在部分版本上有点小坑。我建议你在生产环境里加一个降级方案——如果浏览器不支持Simulcast,就退回到单流模式。
带宽自适应:让视频自己“看路况”
Simulcast解决了“多端适配”的问题,但还有一个更棘手的情况:网络在变。上一秒WiFi满格,下一秒切到4G,再下一秒可能进电梯了。
这时候就需要 带宽自适应。说白了,就是让视频流根据当前网络带宽,动态调整码率和分辨率。
WebRTC内置了带宽估计算法(GCC,Google Congestion Control)。它会根据丢包率、RTT(往返时延)等指标,估算出当前可用带宽,然后通知编码器调整。
但默认算法有时候太保守,或者反应太慢。我遇到过一种情况:网络突然变差,GCC花了5秒才把码率降下来,这5秒里画面全是花屏。
所以,我习惯在应用层再加一层控制。
手动控制码率
你可以通过 RTCRtpSender 的 setParameters 动态调整码率。
// 假设网络变差了,手动降低码率
async function adaptBitrate(sender, newBitrate) {
const params = sender.getParameters();
if (!params.encodings) return;
params.encodings.forEach(enc => {
enc.maxBitrate = newBitrate;
});
await sender.setParameters(params);
console.log(`码率已调整为: ${newBitrate} bps`);
}
什么时候调用这个函数?我建议监听 RTCPeerConnection 的 onicecandidate 或者 oniceconnectionstatechange,结合一些网络质量指标。
结合 Simulcast 做自适应
Simulcast + 带宽自适应,才是完整的方案。举个例子:
- 网络好时:接收端选择 high 流,码率900kbps,画面清晰。
- 网络变差:接收端自动切换到 mid 流,码率300kbps,画面稍模糊但流畅。
- 网络极差:切换到 low 流,码率100kbps,至少能看清人脸。
这个过程不需要发送端重新编码,因为三个流一直在发。接收端只是换了一个“频道”而已。
部署与测试:用Docker + HTTPS 让项目上线
代码写好了,性能也优化了,接下来就是部署。我见过太多人,本地跑得飞起,一部署到服务器就各种问题。原因无非两个:环境不一致、没有HTTPS。
WebRTC 强制要求 HTTPS(或者 localhost),因为 getUserMedia 和 RTCPeerConnection 都是敏感API。没有HTTPS,浏览器直接拒绝。
Docker 能帮我们解决环境一致性的问题。咱们把信令服务器、Nginx(用于HTTPS和静态资源)都容器化。
Dockerfile 示例
# 基于 Node.js 镜像
FROM node:18-alpine
WORKDIR /app
# 安装依赖
COPY package*.json ./
RUN npm install --production
# 复制源码
COPY . .
# 暴露端口
EXPOSE 3000
# 启动信令服务器
CMD ["node", "server.js"]
docker-compose.yml
我习惯用 docker-compose 来编排多个服务。一个信令服务器,一个Nginx做反向代理和HTTPS。
version: '3.8'
services:
signaling:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
nginx:
image: nginx:alpine
ports:
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- signaling
Nginx 配置 HTTPS
你需要先准备好SSL证书。可以用Let's Encrypt免费申请,或者自己生成自签名证书用于测试。
server {
listen 443 ssl;
server_name your-domain.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
location / {
proxy_pass http://signaling:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
测试:别只在Chrome上测
部署完了,怎么测试?我建议至少覆盖这几种场景:
- 多浏览器:Chrome、Firefox、Safari、Edge。每个浏览器的WebRTC实现都有细微差别。
- 多网络环境:WiFi、4G、5G、甚至模拟弱网(Chrome DevTools 里有网络模拟功能)。
- 多端同时接入:至少3-4个参会者,观察CPU和内存占用。
我自己的习惯是,在测试环境里用 chrome://webrtc-internals 查看详细的WebRTC统计信息。丢包率、RTT、码率、分辨率,一目了然。
知识体系总览
下面这张图,帮你理清本章的核心逻辑:
好了,这一章的内容就到这。Simulcast和带宽自适应,是让视频会议从“能用”到“好用”的关键。Docker和HTTPS则是上线的最后一步。把这些都搞定,你的视频会议产品就真正具备了交付能力。