Web 上实现直播推流的方式主要有两种,一种是通过 Flash 推流,一种是通过 WebRTC 推流。目前主流浏览器已经放弃了对 Flash 的支持,Chrome 从 88 版本开始彻底禁用了 Flash。因此,使用 WebRTC 进行直播推流成为了 Web 上最好的选择。
通过 WebRTC 可以让网站在不借助中间媒介的情况下,建立浏览器和浏览器、浏览器和服务器之间的点对点连接,实现视频流和音频流或者其他任意数据的传输。
用在直播的场景下,通过 WebRTC 推流,用户不需要借助 OBS 等推流软件,打开网页就可以发起直播。
推流原理
WebRTC 的底层实现十分复杂,但是 Web 上面的使用很简单,只需要很少的代码就可以实现对等连接和数据传输。浏览器将复杂的 WebRTC 功能抽象为三个主要的 API :
- MediaStream,用来获取音视频流;
- RTCPeerConnection,建立对等连接,用来传输音视频数据;
- RTCDataChannel,用来传输任意应用数据;
发起直播推流只需要用到前面两个 API ,首先获取表示音视频流的 MediaStream 对象,然后建立点对点连接 RTCPeerConnection,通过 RTCPeerConnection 将 MediaStream 推送到直播服务器即可。
直播流的采集
C++音视频开发学习地址:免費】FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发
【文章福利】:免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,大厂面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs),免費分享,如有需要的可以加君羊领取哦!~学习交流君羊994289133点击加入领取资料包
直播流的采集取决于如何获取 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 来生成。
Web OBS的实现
如果只是通过 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 只能被提取一次,第二次调用就会报错,我们需要保存第一次生成的结果。
WebRTC推流SDK
上面简单介绍了 Web 推流的原理,直播流的采集方式以及 Web OBS 的实现过程,基于以上内容和实践经验,我们将这些功能都整合到一起,重点解决浏览器兼容性问题和性能问题,开发了 WebRTC 推流 SDK,力求让用户很轻松就能实现自己的 Web OBS 应用。
通过 WebRTC 推流 SDK,可以进行各种直播流的采集,然后对这些流进行本地混流和预处理,比如画中画布局、添加镜像和滤镜效果、添加水印和文本等,再将处理之后的音视频流推到腾讯云的直播后台,打通了 Web 端采集、处理和推流的全链路。
值得一提的是,对于画面和声音的效果处理,在推流过程中也可以进行,不需要断流就可以调整画面和声音内容,从而达到类似于本地导播的效果。
使用WebRTC 推流 SDK前需要先开通腾讯云直播服务,通过直播控制台地址生成器页面获取 WebRTC 推流地址。由于本地混流和预处理功能对浏览器有一定的性能开销,推流 SDK 默认不启用这些功能,需要调用接口手动开启。开启之前,只能采集一路视频流和一路音频流,开启之后可以采集多路视频流和音频流并进行混合处理。
用户可以根据实际情况选择是否开启该功能,如果只是简单的采集并推流则无需开启,如果是老师上课或者主播直播的场景,需要同时采集多个画面或者调整画面效果,则打开该功能并进行设置。
原文地址:深入解析Web OBS的实现 - 资料 - 音视频开发中文网 - 构建全国最权威的音视频技术交流分享论坛