最近微信小程序的业务场景,使用 WebRTC 对设备进行拉流,针对遇到的一些问题简单记录分享下。
一、音视频传输链路
通过上面的图,可以清楚的看到,分为两条链路
- 设备 SDK 支持 WebRTC,小程序端可以直接跟设备端进行 WebRTC 通信
- 设备 SDK 不支持 WebRTC,小程序端可以通过 WebRTC 跟流媒体通信,流媒体通过自研 p2p 跟设备通信
相比走流媒体转发,小程序直接跟设备通信有两大优势(特别是当 candidate 组合为 host 或 srflx 时)
- 首帧出流速度整体会更快
- 会更加节省云端流量
二、小程序端 WebRTC 兼容性
找到一张腾讯云提供的兼容性表格,仅供参考:
目前,我们测试过的一些机型,持续测试中,后续有遇到不兼容的机型,也会记录在表格
机型 | 系统版本 | 微信版本 | 是否正常 |
---|---|---|---|
三星Galaxy NOTE9/N9600 | 8.1.0 | 8.0.42 | 是 |
OPPO realme Q9 | 11 | 8.0.42 | 是 |
小米10青春版本 | 11 | 8.0.42 | 是 |
华为nova8/ANG-AN00 | 11 | 8.0.42 | 是 |
华为p40 pro | 鸿蒙4.0 | 8.0.41 | 是 |
iPhone 12 | 15.4.1 | 8.0.43 | 是 |
iPhone Xr | 14.0.1 | 8.0.43 | 是 |
PC 环境中其实 WebRTC的支持已经非常好,移动端一直有所欠缺;这几年微信对 WebRTC的支持,移动端 WebRTC的生态预计将越来越好
三、 WebRTC offer 控制
我们的业务场景,只是去获取设备端的音视频,以及对讲的时候,会向设备传输音频,但不会向设备传输视频。因此在协商的时候,需要简单对 offer 处理下
- 第一种方式,通过在 createOffer 的时候,通过
options
进行控制,但offerToReceiveVideo
是控制是否接收视频(需要控制的是是否发送视频),且options
已被废弃,因此行不通 - 第二种方式,使用 addTransceiver 进行控制: addTransceiver('video', { direction: 'recvonly' }),代表仅接收不发送
补充一点,addTransceiver、addTrack、addStream 容易混淆,简单小结:
- addStream 已弃用,可以不考虑
- addTransceiver、addTrack 均可用,但要对 offer 进行控制时,addTransceiver 更加方便
四、移动端 video 视频自动播放
我们的业务场景是,进入页面,直接对设备进行拉流,且需要自动播放画面跟声音。根据之前在 PC 端的经验,我的第一反应是没有办法实现带声音的自动播放,如果是静音 autoplay 才可行。
事实证明,很多时候经验并非事实,一切得根据测试实际结果来看!
先说结论: 经过代码兼容测试,最终的效果是,在 WebRTC 这种场景下,无论是安卓或ios,均可实现带声音自动播放视频。
代码层做了哪些兼容?
javascript
// 引入微信 sdk, 1.6.0 版本
<script src="https://res2.wx.qq.com/open/js/jweixin-1.6.0.js" />
// 调用 config 初始化
window.wx.config({
debug: false,
appId: 'xxx',
timestamp: '',
nonceStr: '',
signature: '',
jsApiList: []
});
// ready 回调中手动调用 video.plsy()
window.wx.ready(() => {
video.play();
})
// video 标签设置为 autoplay
代码层其实就做了上述兼容,就实现了 WebRTC 场景下(注意这里强调了是 WebRTC 场景,普通 mp4 并不行)安卓、ios下带声音自动播放。更准确的说,上述代码,其实只是针对 ios 下部分机型,不能带声音自动播放的兼容。
自动播放端倪
先上一个表格,这样会比较清晰,在未做任何兼容的情况下:
WebRTC video 带声音自动播放视频 | mp4 video 带声音自动播放视频 | |
---|---|---|
安卓 | 可以 | 不可以 |
ios | 部分机型可以 | 不可以 |
PC端 | 不可以 | 不可以 |
通过表格发现,WebRTC video 在未做兼容的情况下,表现跟 mp4 video 是不一样的,两者到底差在哪儿呢?
经过测试得知,主要差别在 video 标签播放视频流的方式不一样:
javascript
// WebRTC video
video.srcObject = mediaStream
// mp4 video
<video src='xxxxxx.mp4' />
使用 'srcObject' 的方式,在安卓跟部分 ios 下,直接就可以带声音自动播放;而使用 'src' 的方式,移动端跟 PC 都是不行的。这部分根据实际测试结果得来,并非官方文档,有小伙伴知道官方规范在哪里的,可以告知一下。
五、ios 自动全屏问题
移动端 ios video 播放时,会自动全屏,添加如下属性即可
html
<video playsinline webkit-playsinline />
六、视频旋转、比例拉伸问题
我们的设备,推流过来的视频比例大部分为 16:9, 且带有旋转角度 90/270,因此针对有旋转角度的设备,需要进行旋转及拉伸操作,才能铺满整个屏幕。
实际做下来的效果还是ok的,因为 16:9 的比例跟大部分的机型比例差的不太多
代码比较简单,纯 css transform(rotate、scale)实现即可
七、监听设备拉流状态、计算视频流 kb/s
测试过程中发现一种场景,WebRTC 建连成功,但画面黑屏。排查下来是设备端没有推流过来,针对这种场景,前端希望感知到,并进行重试按钮的展示。
可以基于 WebRTC RTCPeerConnection getStats 方法实时获取收到的数据,以及解码成功的数据。代码示意如下:
javascript
const stats = await RTCPeerConnectioIns.getStats(null);
stats.forEach((report) => {
if (
report.type === 'inbound-rtp' &&
(report.mediaType === 'video' || report.kind === 'video')
) {
if (report.bytesReceived === 0) {
// 未收到数据
return;
}
if (report.keyFramesDecoded === 0 && report.framesDecoded === 0) {
// 解码失败
return;
}
// 收到数据且解码成功
}
});
另外,产品希望在页面上实时展示一秒钟收到的视频数据(kb/s)。实现方式也是基于上述 API 来做即可。代码示意:
javascript
const stats = await RTCPeerConnectioIns.getStats(null);
let kbReceived = 0;
stats.forEach((report) => {
if (
report.type === 'inbound-rtp' &&
(report.mediaType === 'video' || report.kind === 'video')
) {
kbReceived = Math.trunc(report.bytesReceived / 1024);
}
});
const curTime = Math.trunc(new Date().getTime() / 1000);
// kb/s
const speed = kbReceived / (curTime - 拉流成功时的时间);
八、WebRTC 对讲变声处理
之前在 PC 端的对讲,没有过变声的需求。趁小程序有变声的场景,简单了解了下如何来做变声。
获取麦克风权限兼容
对讲需要获取麦克风权限,需要简单兼容一下 API
javascript
getUserMediaUtil(constrains, success, error, fail) {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
// 最新标准 API
navigator.mediaDevices
.getUserMedia(constrains)
.then(success)
.catch(error);
} else if (navigator.webkitGetUserMedia) {
// webkit 内核浏览器
navigator.webkitGetUserMedia(constrains, success, error);
} else if (navigator.mozGetUserMedia) {
// Firefox 浏览器
navigator.mozGetUserMedia(constrains, success, error);
} else if (navigator.getUserMedia) {
// 旧版 API
navigator.getUserMedia(constrains, success, error);
} else {
fail();
}
}
变声方法
变声实际上跟 WebRTC 没有什么关系,只要前置处理好音频数据,然后塞给 WebRTC 即可
第一种方式是基于 js 自行处理,借助 web audio API。奇舞团这篇文章不错,可以参考看看。对于一些简单的变调且变速的,基于 js 实现比较方便
第二种方式是,借用 wasm 调用 c++ 提供的内部音频处理算法
- 我们的处理流程是 mediastream 转 pcm 塞给 c++ 处理, c++ 处理完回调处理后的 pcm 给前端,前端将 pcm 转 mediastream 喂给 WebRTC 音频通道
- 一般对于变调不变速(我们的场景),或者是变速不变调,直接采用 js 实现相对复杂,此时可借助成熟的算法,配合 wasm 来处理
- wasm 处理音频相对简单,不像视频那么耗 CPU
小结
针对需要在微信小程序视频通信的场景,通过 WebView 使用 WebRTC 或许是一个不错的选择。
相比安装庞大的 APP, 小程序显得更加轻量便捷,比如家用安防摄像头,看猫的摄像头等比较轻量的业务场景。
备注: 文章中若有错误,还请指出,这边会及时更正,感谢。