Web 上实现直播推流的方式主要有两种,一种是通过 Flash 推流,一种是通过 WebRTC 推流。目前主流浏览器已经放弃了对 Flash 的支持,Chrome 从 88 版本开始彻底禁用了 Flash。因此,使用 WebRTC 进行直播推流成为了 Web 上最好的选择。通过 WebRTC 可以让网站在不借助中间媒介的情况下,建立浏览器和浏览器、浏览器和服务器之间的点对点连接,实现视频流和音频流或者其他任意数据的传输。用在直播的场景下,通过 WebRTC 推流,用户不需要借助 OBS 等推流软件,打开网页就可以发起直播。WebRTC 的底层实现十分复杂,但是 Web 上面的使用很简单,只需要很少的代码就可以实现对等连接和数据传输。浏览器将复杂的 WebRTC 功能抽象为三个主要的 API :
- RTCPeerConnection,建立对等连接,用来传输音视频数据;
RTCDataChannel,用来传输任意应用数据;
发起直播推流只需要用到前面两个 API ,首先获取表示音视频流的 MediaStream 对象,然后建立点对点连接 RTCPeerConnection,通过 RTCPeerConnection 将 MediaStream 推送到直播服务器即可。
直播流的采集取决于如何获取 MediaStream 对象,WebRTC 已经为我们准备了相应的接口。
最常用的接口是 navigator.mediaDevices.getUserMedia ,打开麦克风和摄像头设备来采集音视频流,其次是 navigator.mediaDevices.getDisplayMedia ,通过共享屏幕窗口(比如桌面窗口、应用窗口、浏览器标签页)获取音视频流。这两个接口都只能在安全上下文环境(比如 HTTPS 协议或者 localhost 本地开发环境)中使用,否则 navigator.mediaDevices 会返回 undefined 。另外需要注意的是 iOS 14.3 及以上版本才支持在 WKWebView 中使用 getUserMedia 接口以及移动端不支持 getDisplayMedia 接口。以上两个接口获取的流比较固定,推流能提供的内容比较局限。所幸的是WebRTC 提供了 captureStream 接口,这个接口极大的扩充了 MediaStream 的来源,使得推流的内容丰富万变不再单一。调用 HTMLMediaElement 和 HTMLCanvasElement 的方法 captureStream 可以将当前元素正在渲染的内容进行捕获并生成实时流 MediaStream 对象。简单来说,我们用 video 或者 canvas 播放渲染的内容都可以转化成直播流进行推送,因此视频、音乐、图片或者自己绘制的画面都可以作为 MediaStream 的来源。
然而在实际使用 video.captureStream 的过程中,我们踩了一堆坑,发现在不同的浏览器都存在问题:
- Chrome浏览器,从 88 版本开始,通过 video.captureStream 获取的视频流通过 WebRTC 发送之后,接收方无法正常播放视频流。目前为止,chrome 浏览器还没有彻底修复这个问题。唯一的解决办法是关掉浏览器设置中的硬件编码选项,但是对于用户来说不太友好。
- Firefox浏览器,captureStream 方法必须加上前缀 moz ,即 mozCaptureStream 。
Safari浏览器,video 元素直接不支持 captureStream 方法。
最终我们放弃了使用 video.captureStream 方法,各种自定义流都转为用 canvas.captureStream 来生成。如果只是通过 canvas 采集视频和图片转化为实时流,那么只能生成来源单一的视频流。进一步思考,我们完全可以在生成实时流之前通过 canvas 对采集到的画面内容先进行混合和预处理,除此以外,通过 Web Audio 的接口对采集到的声音也能进行同样的混合和预处理。这样一来,我们就可以把 OBS 大部分功能搬到 Web 上面来实现了,无需下载和安装 OBS 软件,只需要打开网页,就可以得到和 OBS 差不多的推流体验。有了 canvas 和 Web Audio 这两个强大的帮手,Web OBS 就有了切实可行的实现方案。下面介绍一下我们设计和实现 Web OBS 的基本思路。首先实现最基本的混流功能,可以将采集的多路流的画面和声音混合到一起,并且自定义每一路画面的大小位置以及每一路声音的音量大小。如下图所示:
然后再实现对于每一路画面单独的预处理效果,比如镜像翻转和滤镜效果,如下图所示:
最后再实现添加水印、文本等附加内容到画面中,就差不多实现了 Web OBS 的所有基本功能了,整体的效果可以参考下图:
利用 canvas 进行画面渲染时,我们使用了 WebGL 来提升渲染性能,利用 GPU 加速渲染速度,并且使用 WebGL Shader 来实现更加丰富的画面特效处理,充分发挥浏览器本身的功能。同时底层设计并实现了一套合成协议,支持mediaStream、HTMLVideoElement、HTMLAudioElement等作为输入源输入,按规则定义视频流和音频流的处理任务,通过数据变化来驱动画面和声音的处理,整个处理过程中可以随时修改合成协议内容,实时输出最新处理结果。这种设计使得后期具备了更好的扩展性,可以方便快速的加入各种新的效果处理,提升了开发效率。
在实现 Web OBS 的过程中也遇到了很多问题和挑战,这里对最常见的几个问题进行一下总结说明。对于 canvas 的渲染一般都是使用的 requestAnimationFrame 来进行画面重绘,使用 requestAnimationFrame 的好处很多,比如提升性能、节约资源等。但是当页面处于未激活状态(隐藏或者最小化)时,requestAnimationFrame 的执行会暂停,这个时候 canvas 的画面内容会静止保持不变,如果正在推流过程中,观众端看到的直播画面就是暂停的,帧数为 0 。因此对于直播的场景来说,requestAnimationFrame 并不太适合。我们采取的办法是监听浏览器的 visibilitychange 事件,如果当前页面是可见状态(document.hidden 属性为 false)就使用 requestAnimationFrame 进行画面绘制,如果当前页面是不可见状态(document.hidden 属性为 true)就改用 setInterval 来进行画面绘制。
- 类似于视频自动播放阻止策略,在用户没有和当前页面进行交互的情况下,WebAudio 创建的 AudioContext 对象默认状态是 suspended,此时对 AudioContext 进行的操作都是无效的。需要在用户和浏览器的交互中,激活 AudioContext,状态变成 running 之后再进行操作。
WebAudio 创建的 AudioContext 对象使用 createMediaElementSource 方法提取 HTMLVideoElement 和 HTMLAudioElement 中的声音时,每一个 element 只能被提取一次,第二次调用就会报错,我们需要保存第一次生成的结果。
上面简单介绍了 Web 推流的原理,直播流的采集方式以及 Web OBS 的实现过程,基于以上内容和实践经验,我们将这些功能都整合到一起,重点解决浏览器兼容性问题和性能问题,开发了 WebRTC 推流 SDK,力求让用户很轻松就能实现自己的 Web OBS 应用。通过 WebRTC 推流 SDK,可以进行各种直播流的采集,然后对这些流进行本地混流和预处理,比如画中画布局、添加镜像和滤镜效果、添加水印和文本等,再将处理之后的音视频流推到腾讯云的直播后台,打通了 Web 端采集、处理和推流的全链路。值得一提的是,对于画面和声音的效果处理,在推流过程中也可以进行,不需要断流就可以调整画面和声音内容,从而达到类似于本地导播的效果。使用WebRTC 推流 SDK前需要先开通腾讯云直播服务,通过直播控制台地址生成器页面获取 WebRTC 推流地址。由于本地混流和预处理功能对浏览器有一定的性能开销,推流 SDK 默认不启用这些功能,需要调用接口手动开启。开启之前,只能采集一路视频流和一路音频流,开启之后可以采集多路视频流和音频流并进行混合处理。用户可以根据实际情况选择是否开启该功能,如果只是简单的采集并推流则无需开启,如果是老师上课或者主播直播的场景,需要同时采集多个画面或者调整画面效果,则打开该功能并进行设置。想要深入了解 WebRTC 推流 SDK 的话,可以点击文末「阅读原文」查看相关产品文档,获取快速接入教程,即刻享受不一样的 Web 推流体验。
在 Web 推流的场景下,我们有时候还需要对流进行进一步处理,例如美颜,美妆以及目前流行的 AR 特效(如下图)。腾讯云目前提供的WebAR SDK可以结合 WebRTC 推流 SDK一起使用,丰富推流的流处理能力。(相关最佳实践文档:https://cloud.tencent.com/document/product/616/71373)
腾讯云音视频在音视频领域已有超过21年的技术积累,持续支持国内90%的音视频客户实现云上创新,独家具备 RT-ONE™ 全球网络,在此基础上,构建了业界最完整的 PaaS 产品家族,并通过腾讯云视立方 RT-Cube™ 提供All in One 的终端SDK,助力客户一键获取众多腾讯云音视频能力。腾讯云音视频为全真互联时代,提供坚实的数字化助力。