直播行业大概在 10 年前就开始兴起了,秀场直播和游戏直播是 pc 时代比较成功的应用场景。现阶段,移动互联网的大规模普及,流量价格越来越便宜,移动视频直播异常火爆,随着各行各业的不断融合,直播带货超高的营业额,明星艺人、销售、秀场网红的涌入,直播行业迎来了空前的繁荣发展。从 pc 直播到渐渐火爆的移动直播,直播技术也在不断地更新迭代,趋于成熟。
本文从直播流的选择、交互优化、快速迭代等方面介绍携程直播技术。
视频直播流程如下图。简单来说,推流端通过视频采集功能,把采集到的视频画面经过一系列的业务特效处理后,进行视频编码推送。拉流端使用流播放器把视频画面播放出来。
前四步我们通常情况下称为推流操作,第五步称为服务分发或者 cdn 分发,第六步称为拉流操作。简化图如下:
推流操作中比较重要的两步是编码和推流。
1)编码是什么
视频编码是压缩和可能改变视频内容格式的过程。
2)编码能做什么
a. 减少占用空间;
b. 兼容性;
在减少占用空间方面:
(数据来源于网络)
在兼容性方面,有时内容已经被压缩到足够的大小,但仍然需要进行编码以实现兼容性。这通常被更准确地描述为代码转换。兼容可能涉及某些服务或程序,这些服务或程序需要某些编码规范。
国内常见的编解码器是 H.265、H.264。
常见的流协议为以下几种:
1)RTMP
RTMP(Real Time MessagingProtocol):实时消息传输协议,是 Adobe 公司为 flash 播放器和服务器之间实现音频、视频和数据传输开发的实时消息传输协议。在 RTMP 协议中,视频必须是 H264 编码,音频必须是 AAC 或者 MP3 编码,且多以 Flv 格式封包。
RTMP 的优势在于:
RTMP 的劣势是:RTMP 是基于 TCP 协议,不会丢包。所以当网络状态差时,服务器会将包缓存起来,导致累积的延迟;待网络状况好了,就一起发给客户端。
2)HLS
HLS(HTTPLive Straming): 是苹果公司实现的基于 HTTP 的流媒体传输协议。它将整个流分为多个小文件来下载,客户端只要不停的按顺序播放从服务器获取的文件,就实现了直播。
HLS 的优势是:客户端支持简单,只需要支持 HTTP 请求即可。并且 HTTP 协议很方便通过防火墙或者代理服务器。CDN 支持良好。由于是苹果公司提出的,所以在苹果的全系列产品都支持。
HLS 的劣势:相比 RTMP 这类长连接协议,HLS 的延时较高, 难以用到互动直播场景。
3)WebRTC
WebRTC(Web Real TimeCommunication):是一个支持浏览器进行实时语音、视频对话的开源协议。基于 UDP,即使在网络信号一般的情况下也具备较好的稳定性。
WebRTC 的优点:开发者使用简单的 HTML 标签和 JavaScriptAPI 就能够实现 Web 音/视频通信的功能。
WebRTC 的缺点:WebRTC 中很多的参数都是由 GIPS 公司的工程师们依靠经验所设定的值,这就会出现卡顿、延时、回声、丢包、多人视频不稳定等问题。WebRTC 缺乏服务器方案的设计和部署。对 Native 开发支持不够。
4)HTTP-Flv
HTTP-Flv:是一种将直播流模拟成 flv 文件,通过 http 协议进行下载的模式实现流媒体传输的协议。
它结合了 RTMP 的低延时,以及可以复用现有 HTTP 分发资源的流式协议。优势在于可以在一定程度上避免防火墙的干扰,可以使用 HTTPS 做加密通道,很好的支持移动端。
缺点在于由于它的传输特性,会让流媒体资源缓存在本地客户端,在保密性方面不够好。因为网络流量较大,它也不适合做拉流协议。
协议 | http-flv | RTMP | HLS | WebRTC |
---|---|---|---|---|
传输方式 | HTTP长连接 | Tcp | http短连接 | UDP |
视频封装格式 | FLV | FLV | TS文件 | Track分片 |
原理 | 同RTMP,使用HTTP协议 | 每个时刻的数据收到后立即转发 | 集合一段时间的数据,生产TS切片并更新m3u8索引 | Udp 传输协议,保证低延时和及时性。Client-client之间传输,要解决NAT 穿透问题。 |
延时 | 低 1-5s | 低 1-5s | 高 10-20秒 | 低1-2s |
数据分段 | 连续流 | 连续流 | 切片文件 | P2P数据交换 |
Html5 | 支持 | 不支持 | 支持 | 支持 |
其他 | 需要Flash技术支持, | 网络质量要求高 | 要解决NAT,socket建立等问题 |
我们选择 RTMP 作为主要的流协议的原因有:
1)RTMP 是编码器输入的工业标准协议,基本上所有的编码器(摄像头等)都支持 RTMP 输出。
2)RTMP 适合长时间播放的优势,可以保证尽可能的减少用户重连的次数。
3)直播场景互动性交高,对时延要求高。RTMP 通常情况下可以做到 3 秒延迟,满足大多数场景(hls 大概 10 秒)。
4)WebRTC 对浏览器支持较好,对 native 支持不够,需要做大量的开发工作。
5)由于开源软件和开源库的支持稳定完整(OBS 软件,开源的 librtmp 库,服务端有 nginx-rtmp 插件),RTMP 在国内流行度很高,技术相对成熟。
目前市面上有很多云直播厂商。可以综合考虑推流协议,时延要求、推拉流费用,SDK 的 size 以及扩展直播场景等方面来选择适合自己的 sdk。
当我们需要建立一个直播的时候,我们需要做什么呢?
简单的来说分 3 步。
第一步:从 sdk 中拿出推流 Manager,设置预览 View,设置推流地址。
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.live_push);
livePusher = new LivePusher();
videoView = findViewById(R.id. live_video_preview);
livePusher. startCameraPreview(videoView);
String pushUrl = "rtmp://...";
livePusher.startPusher(PushUrl);
}
第二步:使用拉流 Manager,设置播放预览 view,设置拉流地址和流类型(RTMP、flv 等)。
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.live_pull);
livePlayer = new LivePlayer();
playerView = findViewById(R.id.live_video_view);
String pullURl = "rtmp://...";
livePlayer.setPlayerView(playerView)
livePlayer.startPlay(pullURl, PLAY_TYPE_LIVE_RTMP)
}
第三步就可以愉快地观看直播了。
携程直播就是在这个基础之上,进行了复杂的业务开发。视频推流和拉流是需要调用 Native 直播 sdk 的方法,所以需要保留在 Native 中。页面上的互动区域需要更快速的迭代方式,所以选择了 RN。
携程直播作出以下的分层结构:
视频直播前端框架图
1)Lib
这一层主要放置整个直播项目通用的类。
ClientProxy:网络请求的通用字段封装和返回的常规错误码处理、序列化处理、通用 Log 日志控制等。
PushManger 和 PullManger 使用了 Proxy 设计模式用于减少 sdk 和业务代码的耦合。使用单例模式保证多个直播的配置统一。
Event:跨平台事件传递类(EventBus),RN、Hybrid 的事件都最终调用 native 的 Event 方法发送事件来保证多平台事件传递的可能。
RN:
Event.sendEvent("EventName",{ params: 0 });
Native:
CtripEventCenter.getInstance().sendMessage(EventName,jsonObject)
底层都是调用 Native 的 EventBus。
IMManger:消息的管理类。控制直播间的弹幕消息,开始结束消息以及礼物等消息的分发。
2)Page
把推流和拉流页面称为 Page。主要作用有两个,首先调用 SDK 做推拉流。其次,在页面中对 RN 和 Native 进行交互,例如从消息中获取流状态、礼物或者其他的消息。然后给 RN 发 Event 事件或者调用 Native 方法来完成相关消息的后续动作。
Page 中注册消息监听。
interface CTLiveChatMessageListener {
fun startLive()
}
Page.registerCTLiveChatMessageListener(listener)
在 listener 中使用 Event 分发事件。
3)View 层
Page 上挂载了一个透明的 RNView 作为直播中交互 View。主要负责渲染业务交互 view,比如头像名称、评论列表、礼物动画、商品卡片、分享等。
在 page 的 onCreate 的时候把 view 挂载上去。
manager.beginTransaction().add(R.id.live_crn_fragment, rnFragment,
"livePage").commitAllowingStateLoss()
然后页面和逻辑放到 RN 中。
效果图上,摄像头的内容为原生 Native,其他的可见为 RNVIew。
在 Native-RN 混合开发过程中,我们遇到了一些棘手的问题:
1)在多次唤醒直播间,或者同时打开多个直播间时,会存在画面和声音对不上,或者出现多个声道的问题。为了解决这个问题我们把直播间做成单例,保证整个 app 的运行过程中只存在一个直播间。
2)覆盖在直播预览页面上面的交互 RNView 设置为透明背景不生效问题。我们在 RN 的 render 返回一个透明 View,但是在页面上还是出现了白色的底色。这个时候需要检查一下 fragment 是否挂载在一个白色 View 上。
3)在 Page 初始化的时候发送 Event 事件,但是 RN 没有收到的问题。这个时候 RN 容器可能还没有创建完成,我们需要保证发送事件的时机在 RN 容器创建完成之后。
视频直播在近几年是一个比较火爆的技术点。直播的场景每年都在迅速地更新中。我们需要使用一种更快速的迭代方式,所以需要在优化时把稳定的东西使用 native 封装成基础页面,在保证底层稳定的前提下,选择跨平台的技术栈进行快速的页面迭代开发。ReactNative 或者 Flutter 等跨平台开发语言都是不错的选择。
作者介绍:
鹏程,携程 Android 开发工程师,Android google jetpack 和 kotlin 语言的拥护者。
本文转载自公众号携程技术(ID:ctriptech)。
原文链接:
领取专属 10元无门槛券
私享最新 技术干货