如何通过WebSocket在100ms内将240x240px图像发送至其他客户端
现有代码的核心性能问题
你当前的写法存在两个非常大的无意义损耗,是帧率低的核心原因之一:
- 把
Uint8ClampedArray转成普通数组后再做JSON序列化:原始像素是单字节存储的0-255数值,JSON序列化后每个数字会变成1-3位的文本字符,再加逗号、括号等分隔符,体积直接膨胀3-4倍,还额外消耗了序列化/反序列化的CPU资源 - 传输未压缩的原始RGBA像素数组:单张1080P的RGBA原始像素大小就有8MB左右,哪怕只跑15帧,带宽需求就超过900Mbps,完全不可能达到高帧率
客户端图像压缩方案
方案1:转压缩图像格式传输(优先推荐)
直接通过canvas将画面转成WebP/JPEG格式的二进制流后传输,是性价比最高的压缩方案,70%质量的WebP体积只有原始RGBA像素的1/10~1/20,代码示例如下:
ctx.drawImage(video, 0, 0, canvasDimension, canvasDimension); // 第二个参数指定压缩格式,优先用WebP,兼容性不够再换image/jpeg // 第三个参数是质量,0-1之间,可根据清晰度要求调整 canvas.toBlob((blob) => { // 直接发送二进制Blob,不要走JSON序列化 ws.send(blob); }, 'image/webp', 0.7);
如果需要附带消息类型、图像宽高等元数据,可以在二进制流前拼接固定长度的头信息,比如前1字节存消息类型,第2-5字节存宽度,第6-9字节存高度,后面接图像二进制内容,完全避免JSON的额外开销。
方案2:像素数据压缩(仅适用于必须传输原始像素的场景)
如果业务要求服务端必须拿到原始像素数据,可以用pako等JS压缩库对原始像素二进制做gzip/deflate压缩,压缩比通常能到1/3~1/5:
import pako from 'pako' ctx.drawImage(video, 0, 0, canvasDimension, canvasDimension); const rawData = ctx.getImageData(0, 0, canvasDimension, canvasDimension).data; // 直接压缩原始二进制,不要转普通数组 const compressedData = pako.deflate(rawData); ws.send(compressedData);
服务端收到后做对应的解压缩即可拿到原始像素数组。
WebSocket传输优化通用技巧
- 永远用二进制帧传输数据,不要用JSON序列化二进制内容:WebSocket原生支持二进制帧,文本协议的额外开销极高,元数据全部通过自定义二进制头传递
- 开启
permessage-deflate扩展:绝大多数现代浏览器和WebSocket服务端都支持这个扩展,开启后会自动对传输内容做流压缩,不需要额外写业务代码 - 客户端显式设置二进制类型:初始化WebSocket后添加
ws.binaryType = 'blob'或者ws.binaryType = 'arraybuffer',避免自动转文本带来的损耗 - 合理控制分辨率和帧率:业务允许的前提下,优先降低分辨率和帧率,传输压力会线性下降,比如从1080P30帧降到720P15帧,压力直接降到原来的1/6
- 合并小消息:多个小的控制帧尽量合并成一个帧发送,减少TCP帧头和WebSocket帧头的额外开销
- 避免多余的数据拷贝:不要做类似
[...new Uint8ClampedArray()]这种无意义的类数组转普通数组操作,直接操作原始二进制视图即可
内容的提问来源于stack exchange,提问作者Crupeng
相关产品推荐
相关产品推荐

