世界上杀伤力最大的武器不是核弹,而是AK-47,这款由卡拉什尼科夫所设计的突击步枪,全世界一共生产了约一亿支。它具有不俗的杀伤力和极为优秀的可靠性。从不卡壳,不易损坏,不管是沙漠还是雨林,都能稳定地倾泻火力。并且操作非常简单,以至于很多非洲兄弟在射击时会将其举过头顶,因为他们相信这种动动手指就能摧毁对方的武器,完全是“白人的巫术”。
抱着同样的想法,我们跟微信团队一起,致力于在小程序上打造出一款效果出色、稳定可靠并且简单易用的音视频组件。本文接下来的部分,我希望能用简单的文字,让您了解那些界面丰富且体验优异的音视频功能背后的故事。
"分子由原子组成"
无论多么复杂的音视频功能,我们都可以将其拆解为两个基本“原子”的组合,一个是音视频上行,一个是音视频下行。
音视频上行
音视频上行,就是把自己的声音和画面传送出去。只有这样,别人才能看到您的影像,听到您的声音:
- 采集预处理 最开始,我们要对摄像头的画面进行捕获,对麦克风的声音进行采集。但是,原生采集和捕获的画面和声音是需要进行预处理的,直接采集的画面可能有很多噪点,所以我们要进行图像降噪; 100% 还原真实的皮肤可能并不符合人们的预期,所以我们需要进行磨皮和美颜;直接采集的声音可能也有很多的环境噪音,所以我们需要进行前景和后景音的分离然后进行底噪抑制。
经过预处理之后的画面和声音相比于原始采集的一般会有较大改善,因为所有的预处理都是以“讨好”人类的视听体验为目的,所以这一看似不起眼的部分会吸引很多公司在其上做不少的技术投入。举个身边的例子,以 LCD 平板电视为例,SONY虽然没有自家的液晶面板(以中国台湾和大陆液晶面板为主),却能在总体效果上一直领先其它公司,其背后的秘密就是在图像处理(基于图像数据库做超分辨率显示)和背光技术(所有动物的眼睛都是对亮度最为敏感)上的不间断的积累和投入。
- 编码和发送 画面和声音都经过“粉饰”之后,就可以送给编码器进行编码压缩了。编码器的工作是将一张张的画面和一段段的声音压缩成 0101001... 的二进制数据,而压缩后的体积要远小于压缩前。最后要做的工作就是将编码后的数据通过网络模块发送出去。在在线直播场景中,一般采用的网络协议都是基于TCP的,而在实时通话场景中,所采用的网络协议则是 UDP 为主。
- <live-pusher> 小程序在新版本中加入了 <live-pusher> 标签用于实现音视频上行, 它支持两种模式:直播(标清-SD、高清-HD、超清-FHD) 和 RTC,前者用于直播推流,后者则用于实时音视频通话。<live-pusher> 的 Live 模式即 TXLivePusher::setVideoQuality 中的前三个档位之一。
<live-pusher> 的 RTC 模式即 TXLivePusher::setVideoQuality 中的 REALTIME_VIDEOCHAT。
场景 | mode | min-bitrate | max-bitrate | audio-quality | 说明 |
---|---|---|---|---|---|
标清直播 | SD | 300kbps | 800kbps | high | 窄带场景,比如户外或者网络不稳定的情况下适用 |
高清直播 | HD | 600kbps | 1200kbps | high | 目前主流的APP所采用的参数设定,普通直播场景推荐使用这一档 |
超清直播 | FHD | 600kbps | 1800kbps | high | 对清晰度要求比较苛刻的场景,普通手机观看使用 HD 即可 |
视频客服(用户) | RTC | 200kbps | 500kbps | high | 这是一种声音为主,画面为辅的场景,所以画质不要设置的太高 |
车险定损(车主) | RTC | 200kbps | 1200kbps | high | 由于可能要看车况详情,画质上限会设置的高一些 |
多人会议(主讲) | RTC | 200kbps | 1000kbps | high | 主讲人画质可以适当高一些,参与的质量可以设置的低一些 |
多人会议(参与) | RTC | 150kbps | 300kbps | low | 作为会议参与者,不需要太高的画质和音质,audio-quality 的 low 模式,其音质和延迟感都要会比 high 模式差很多 |
音视频下行
也叫播放(play),即从云端拉取编码后的音视频数据流进行解码和播放:
- 抖动缓冲(jitterbuffer) 网络不是完美的,所以网速会有波动,如果服务器来一段数据就播一段数据,那么网络稍微一波动,画面和声音就会表现出卡顿。抖动缓冲技术,就像是为网络过来的数据准备一个小的蓄水池,音视频数据先在这里暂存一小会儿再送去播放,这样就可以在网络不稳定时有一定的“应急”数据可以使用。
- 解码和播放 解码就是把压缩后的音视频数据还原成图像和声音,然后进行渲染和播放。我们采用了 openGL 进行画面的渲染,使用 iOS 和 Android 的系统接口来播放声音。
有人经常问我:“在播放器端怎么改变画面的清晰度?” 这个问题回答起来既简单又复杂。简单回答是说播放器并不能改变画面的清晰度,清晰度在视频编码之后就确定了。复杂的回答则是,通过跟云端的配合,确实可以在播放器上改变清晰度。因为腾讯云的每一条直播流都支持多分辨率实时转码,开启这个功能后,就可以在播放器上根据用户的选择播放不同的 url,进而实现不同清晰度的切换。
- <live-player> 小程序在新版本中加入了 <live-player> 标签用于实现音视频下行, 它支持两种模式:live 和 RTC,前者用于直播播放,后者则用于实时音视频通话。
<live-player> 的 Live 模式即 TXLivePlayer::startPlay 中的 LIVE_RTMP 或 LIVE_FLV。 <live-player> 的 RTC 模式即 TXLivePlayer::startPlay 中的 PLAY_TYPE_LIVE_RTMP_ACC。
有了简单的“原子”,我们就可以进行初步的化学反应,从简单到复杂地构建出一些“分子”结构,接下来,我们先从一个最简单的应用场景开始 —— 单向音视频。
单向音视频:在线培训
技术解读
在线培训是一个非常经典的单向音视频场景,您只需要简单的将负责音视频上行的 <live-pusher> 和负责音视频下行 的 <live-player> 组合在一起即可。
<live-pusher> 能够将讲师的影像和声音推送到云端(一般也可以使用专业的采集设备),腾讯云本身就相当于一个 信号放大器,它负责将一路音视频流扩散到位于全国各地的 CDN 机房,如此一来,观众端的 A B C 都可以在离自己最近的云服务器上,使用 <live-player> 拉取到稳定和流畅的音视频流。
由于原理简单、易于维护且支持几百万同时在线的高并发观看,所以从在线教育到体育赛事,从游戏直播到花椒映客,都是基于这种技术实现的。
对接步骤
在讲师端创建一个 <live-pusher> 标签,并将 mode 设置为 HD,技术参考文档见 DOC。 学员一端的小程序只需要创建一个 <live-player> 标签,并将 mode 设置为 live 即可,技术参考文档见 DOC。
但这种技术有个严重的缺陷,就是单向延时无法控制在 1s 以内,一般都是在 2秒-5秒 左右,甚至更长,那么在一些对时延要求很苛刻的场景下就不再适用了,比如下面这个场景:
单向低延时:在线电玩
技术解读
在线夹娃娃在今年特别流行,玩家可以远程控制抓钩来抓娃娃,所以需要将单向延时控制在 500ms 以内,而且越低越好。要达到这么低的要求,普通的直播技术就不行了,我们需要新引入两个新的科技点:延时控制 和 UDP加速。
- 延时控制 网络不是完美的,网络是波动的。在有波动的网络下,服务器上的音视频数据并不是稳稳的来到您的手机上,而是忽快忽慢。慢的时候您可能会看到卡顿,快的时候就会产生堆积,而堆积的后果就是延时的增加。所以,我们需要采用延迟控制技术,它的原理很简单,当网络慢的时候就播的慢一点,当网络快的时候就播得快一点,这样就起到一定的缓冲作用。当然,真正实现时就会发现,声音是个很不听话的“孩子”,要处理好声音的效果是一个非常高难度的技术活。
- UDP加速 既然网络不那么完美,总是时快时慢,那我们是不是可以改善一下呢?在经典的单向音视频方案中,一般采用的都是 TCP 协议,因为它简单可靠且兼容性极好。然而 TCP 天然就有时快时慢的坏毛病,所以我们需要用 UDP 协议替代之,相比于设计目标定位于可靠传输的 TCP 协议,自定义协议栈的 UDP 可以做得更稳且更快。
现在我们已经拥有了两个新的科技点,接下来就把它用到我们的小程序中:
对接步骤
玩家创建一个 <live-player> 标签,并将其 mode 设置为 RTC,此时小程序会开启延时控制 和 UDP加速,同时,src 要使用 rtmp:// 协议的播放地址,并且要为播放地址添加防盗链签名。技术参考文档见 DOC。 观众也是要创建一个 <live-player> 标签,并将其 mode 设置为 live,此时 src 指定普通的 flv 协议播放地址就可以了,技术参考文档见 DOC。
双向音视频:车险定损
技术解读
有了单向低延时技术,那么双向视频通话自然也就比较简单了,只需要通话的双方 A 和 B 各自拉通一路低延时链路就可以了。
是吗?
虽然思路正确,但实现上不是那么简单的,因为我们还需要引入额外的几个科技点:
- 回音消除 在双向视频通话中,用户自己手机的麦克风会把喇叭里播放的声音再次记录下来,如果不将其抹除掉,这些声音会被反送给对端的用户,从而形成回声。
- 噪声抑制 噪声抑制的目的是将用户所处环境里的背景噪音去除掉,好的噪声抑制是回音消除的前提,否则声学模块无法从采集的声音辨别出哪些是回声,哪些是应该被保留的声音。
- Qos流控 网络不可能一直都很完美,尤其是中国大陆地区的上行网速一直都有政策限制。Qos流控的作用就是预测用户当前的上行网速,并估算出一个适当的数值反馈给编码器,这样一来,编码器要送出的音视频数据就不会超过当前网络的传输能力,从而减少卡顿的发生。
- 丢包恢复 再好的网络也难免会有丢包的情况,尤其是 WiFi 和 4G 等无线网络,由于传输介质本身就不是可以独享的,所以一旦受到干扰,或者高速运动都会产生大量的丢包,这时就需要引入一些丢包恢复技术,将失去的数据尽量补救回来。
现在我们又获得了四个新的科技点,接下来我们把它用到我们的小程序中:
对接步骤
A 创建一个 <live-pusher> 标签,mode 设置为 RTC,并将对应的低延时播放地址 urlA 交给 B。 B 创建一个 <live-player> 标签,mode 设置为 RTC,src 指定为 urlA。 B 创建一个 <live-pusher> 标签,mode 设置为 RTC,并将对应的低延时播放地址 urlB 交给 A。 A 创建一个 <live-player> 标签,mode 设置为 RTC,src 指定为 urlB。此处所需的技术参考文档见 实时音视频。
多人音视频:视频会议
技术解读
既然双人视频通话已经搞定了,是不是多人也就照葫芦画瓢就可以了?您看,我们只需要将 A 和 B 之间的 url 置换,变成 A、B、C 甚至更多人之间的 url 置换,不就可以了吗?
是吗?
虽然思路正确,但是真正要将功能做到商用级别,仅依靠简单的 url 交换是非常粗糙的,我们需要继续引入额外的两个科技点:
- 房间管理 以上图所示的 A B C 之间的多人视频场景为例,要让每一个人都很清楚其它人的状态(比如低延时播放url,以及当前是否有上行等等),这个事情可是非常困难的,搞不好就容易出现各方信息不对齐。对于更复杂一点的情况,比如当有第四个人 D 进来的时候,或者第五个人 E 进来又出去的时候,这种信息同步几乎就是一场噩梦。
最好的办法就是把参会人的状态和信息都收拢在服务器端,构造一个 房间 的概念,这样就可以确保参会人都能从服务端获得同样的信息,而不需要各自去维护。
- IM 系统 当有新的参与者进入房间,或者有人离开时,就需要对房间里的人进行信息广播,这就需要一个不错的 IM 系统负责收发消息。比如当 D 进入时,就可以向房间内的其它成员广播这个 “I'm coming” 的事件,这样 A B C 就可以在自己的 UI 上展示 D 的视频画面了。
现在我们又获得了两个新的科技点,接下来我们把它用到我们的小程序中:
对接步骤
跟之前几个科技点不同,小程序并没有默认提供房间管理和 IM 系统的微信内实现,因为房间管理跟客户业务耦合太紧密,腾讯云通讯 IM 服务也已经有了小程序端的 javascript 组件。
所以我们提供了一个叫做 RTCRoom 的 javascript 组件用于降低这里的实现复杂度,您可以在我们的 小程序源码 中找到 rtcroom.js。
会议发起者 A 可以用 rtcroom.createRoom 创建一个房间。 参会者 B 和 C 可以用 rtcroom.enterRoom 加入一个房间。 这期间,A B C 都可以通过 onMemberEnter 和 onMemberLeave 了解房间内参会者的进进出出。 如果参会者 B 和 C 想要离开,只需要用 rtcroom.leaveRoom 通知其他人即可。 会议发起者 A 可以随时用 rtcroom.destoryRoom 将当前的房间解散掉。
此处所需的技术参考文档见 RTCROOM。