11、直播流处理:HLS与DASH协议在TV应用中的实现
做TV开发,绕不开直播流。
你想想看,用户买智能电视回家,最常干的事是什么?看直播。不管是新闻联播、体育赛事,还是春晚,直播才是TV端真正的核心场景。我这些年做过的TV项目,十个里有八个都跟直播流打交道。
今天我们就来聊聊HLS和DASH这两个协议。说白了,它们就是视频流的分片打包方式。HLS是苹果搞的,DASH是国际标准。在TV端,两个都得支持,因为不同的内容提供商用的协议不一样。
11.1 HLS协议:苹果的"老将"
HLS,全称HTTP Live Streaming。这玩意儿虽然年头不短了,但在TV端依然很能打。
它的核心思路很简单:把一整段视频切成一个个小片段,每个片段大概2-10秒。播放器下载一个,播一个,再下载下一个。这样用户就不用等整个视频下载完,点开就能看。
HLS的核心文件结构:
- m3u8文件:相当于目录索引,告诉播放器有哪些码率的视频流
- ts文件:视频切片,实际播放的内容
- key文件:加密密钥(如果用了加密)
我在项目中遇到过一个问题:有些低端电视解码ts格式特别慢。后来发现是硬件解码器对ts的兼容性不好。嗯,这里要注意,如果你的目标设备是老旧电视,可以考虑用fmp4格式替代ts。
11.2 DASH协议:国际标准
DASH,Dynamic Adaptive Streaming over HTTP。它比HLS晚出生几年,但设计上更灵活。
DASH用的是mpd文件做索引,视频切片通常是fmp4格式。它的自适应码率算法更智能,切换更平滑。
个人经验:我建议在TV端优先使用DASH。原因很简单——DASH的fmp4格式在Android TV上解码效率更高,而且支持更多的编码参数调整。当然,如果内容源只提供HLS,那也没得选。
11.3 自适应码率:TV端的关键
自适应码率,说白了就是根据网络状况自动切换视频清晰度。你想想看,用户家的WiFi信号时好时坏,总不能看着看着就卡住吧?
Android TV的ExoPlayer内置了自适应码率算法。但说实话,默认算法在TV端表现一般。我习惯自己调整参数。
// 自定义自适应码率策略
TrackSelection.Factory adaptiveTrackSelectionFactory =
new AdaptiveTrackSelection.Factory.Builder()
.setMinDurationForQualityIncreaseMs(10000) // 至少10秒才升码率
.setMaxDurationForQualityDecreaseMs(5000) // 5秒内降码率
.setMinDurationToRetainAfterDiscardMs(3000) // 降码率后保留3秒
.setBandwidthFraction(0.7f) // 使用70%的带宽
.setBufferedFractionToLiveEdgeForQualityIncrease(0.5f)
.build();
为什么要这么调?
TV端跟手机不一样。手机你盯着小屏幕,码率切换不太明显。但电视屏幕大,码率一降,满屏的马赛克,用户立马就发现了。所以我让升码率更保守,降码率更激进。说白了,宁可降得快一点,也别让用户看马赛克。
避坑指南:我曾经在某个项目中,自适应码率算法没调好,导致用户在观看体育赛事时频繁切换清晰度。足球比赛画面本来就快,码率一切换,画面就花一下。后来我把升码率的等待时间从5秒改到15秒,问题就解决了。
11.4 ExoPlayer集成HLS和DASH
ExoPlayer是Google官方的媒体播放器,对HLS和DASH支持得最好。我建议TV开发都用它,别用MediaPlayer。
// 创建播放器
ExoPlayer player = new ExoPlayer.Builder(context)
.setMediaSourceFactory(
new DefaultMediaSourceFactory(context)
.setLiveTargetOffsetMs(5000) // 直播延迟5秒
.setLiveMinOffsetMs(3000)
.setLiveMaxOffsetMs(10000))
.build();
// 加载HLS流
MediaItem hlsItem = MediaItem.fromUri(
"https://example.com/live/stream.m3u8");
player.setMediaItem(hlsItem);
player.prepare();
// 加载DASH流
MediaItem dashItem = MediaItem.fromUri(
"https://example.com/live/stream.mpd");
player.setMediaItem(dashItem);
player.prepare();
代码看着简单吧?但实际坑不少。
11.5 直播延迟优化
直播延迟是个老大难问题。HLS默认延迟在20-30秒,DASH好一点,但也有10-15秒。对于体育直播来说,这个延迟太要命了。你想想,隔壁邻居都喊"进球了",你这边还在等,多尴尬。
降低延迟的方法:
- 缩短切片时长:把ts或fmp4的切片时长从6秒降到2秒
- 降低缓冲区:播放器缓冲区从30秒降到5秒
- 使用低延迟模式:HLS的LL-HLS,DASH的UTCTiming
// 低延迟直播配置
MediaItem.Builder builder = new MediaItem.Builder()
.setUri(uri)
.setLiveConfiguration(
new MediaItem.LiveConfiguration.Builder()
.setTargetOffsetMs(3000) // 目标延迟3秒
.setMinOffsetMs(2000) // 最小延迟2秒
.setMaxOffsetMs(5000) // 最大延迟5秒
.build());
player.setMediaItem(builder.build());
我的建议:低延迟模式对网络要求很高。如果你的用户群体网络条件一般,别把延迟压得太低。我曾经试过把延迟压到2秒,结果用户频繁卡顿,体验更差。后来折中到5秒,效果最好。
11.6 直播流的生命周期管理
TV应用跟手机不一样,用户可能一开就是几个小时。直播流的生命周期管理就特别重要。
public class LivePlayerManager {
private ExoPlayer player;
private Handler mainHandler;
private boolean isLiveStream = false;
// 监听直播流状态
private final Player.Listener listener = new Player.Listener() {
@Override
public void onPlaybackStateChanged(int playbackState) {
switch (playbackState) {
case Player.STATE_BUFFERING:
// 缓冲中,显示加载动画
showBufferingIndicator();
break;
case Player.STATE_READY:
// 准备就绪,隐藏加载动画
hideBufferingIndicator();
break;
case Player.STATE_ENDED:
// 直播流不应该结束,检查是否断流
if (isLiveStream) {
reconnect();
}
break;
}
}
@Override
public void onPlayerError(PlaybackException error) {
// 直播流出错,自动重连
if (isLiveStream) {
scheduleReconnect();
}
}
};
private void scheduleReconnect() {
// 指数退避重连
int delay = Math.min(++retryCount * 2000, 30000);
mainHandler.postDelayed(this::reconnect, delay);
}
}
这里有个细节:直播流如果断了,不能像点播那样直接报错。用户正看着球赛呢,突然黑屏,那不得骂娘?所以一定要做自动重连,而且重连间隔要逐渐增大,别把服务器打崩了。
11.7 多码率切换的UI体验
TV端的多码率切换,UI交互跟手机完全不同。手机可以点一下设置按钮,弹个菜单选清晰度。电视遥控器操作,得用方向键上下选。
我习惯的做法:
- 在播放器底部显示一个清晰度选择条
- 用左右方向键切换
- 切换时显示当前码率信息
- 切换过程中保持画面不黑屏
// 获取可用码率
TrackGroupArray trackGroups = player.getCurrentTrackGroups();
for (int i = 0; i < trackGroups.length; i++) {
TrackGroup group = trackGroups.get(i);
if (group.type == C.TRACK_TYPE_VIDEO) {
for (int j = 0; j < group.length; j++) {
Format format = group.getFormat(j);
int bitrate = format.bitrate;
int width = format.width;
int height = format.height;
// 显示给用户:1080p、720p、480p等
}
}
}
// 手动切换码率
player.setTrackSelectionParameters(
player.getTrackSelectionParameters()
.buildUpon()
.setMaxVideoSize(1920, 1080)
.setMaxVideoBitrate(5000000) // 5Mbps
.build());
注意:手动切换码率时,ExoPlayer会重新请求新的分片。如果网络慢,可能会出现短暂的黑屏。我建议在切换时先预加载下一个码率的分片,等准备好了再切换。这样用户就感觉不到黑屏了。
11.8 直播流的DRM加密
TV端的直播流,很多都带了DRM加密。尤其是付费频道,不加密谁还交钱?
Android TV支持Widevine DRM,这是最主流的方案。集成起来也不复杂。
// 配置DRM
MediaItem.Builder builder = new MediaItem.Builder()
.setUri(uri)
.setDrmConfiguration(
new MediaItem.DrmConfiguration.Builder(
C.WIDEVINE_UUID)
.setLicenseUri("https://license.example.com/wv")
.setLicenseRequestHeaders(
Collections.singletonMap("Authorization", "Bearer token123"))
.build());
player.setMediaItem(builder.build());
DRM这块有个坑:不同电视厂商对Widevine的支持级别不一样。索尼、三星的高端电视支持L1级别,可以播4K内容。但有些国产电视只支持L3,只能播720p。你想想看,用户买了4K电视,结果只能看720p,那不得退货?
所以,我建议在应用启动时先检测设备的DRM能力,然后根据能力选择对应的码率。
11.9 直播流的性能监控
直播流跑起来之后,不能就不管了。你得监控它的运行状态,出了问题及时处理。
// 获取播放统计信息
AnalyticsListener analyticsListener = new AnalyticsListener() {
@Override
public void onBandwidthEstimate(
EventTime eventTime, int totalLoadTimeMs,
long totalBytesLoaded, long bitrateEstimate) {
// 当前带宽估计
Log.d("LiveStream", "带宽: " + bitrateEstimate + " bps");
}
@Override
public void onDroppedVideoFrames(
EventTime eventTime, int droppedFrames, long elapsedMs) {
// 丢帧统计
if (droppedFrames > 10) {
// 丢帧太多,考虑降码率
requestLowerBitrate();
}
}
@Override
public void onVideoSizeChanged(
EventTime eventTime, int width, int height,
int unappliedRotationDegrees, float pixelWidthHeightRatio) {
// 视频分辨率变化
Log.d("LiveStream", "分辨率: " + width + "x" + height);
}
};
这些监控数据,我一般会做成一个调试面板,在开发阶段打开。上线后可以关掉,但保留关键日志,方便排查问题。
11.10 知识体系总览
说了这么多,我们来梳理一下直播流处理的核心知识体系:
这张图把直播流处理的核心模块都串起来了。从上到下,协议层、播放器层、功能层、监控层,每一层都有各自的职责。你在实际开发中,可以对照这张图来检查自己的实现是否完整。
直播流处理这块,说难不难,说简单也不简单。关键是要理解协议的本质,然后根据TV端的特点做适配。我做了这么多年,最大的体会就是:别照搬手机端的方案,TV端有TV端的玩法。
好了,这一章的内容就到这里。如果你在实际开发中遇到什么问题,欢迎来交流。
本章要点回顾:
- HLS和DASH是两种主流的直播流协议,TV端都要支持
- 自适应码率算法需要针对TV端大屏幕特点做调优
- ExoPlayer是Android TV上最好的直播流播放器
- 直播延迟优化要在流畅度和实时性之间找平衡
- DRM加密和性能监控是上线前必须做好的工作
公众号:蓝海资料掘金营,微信deep3321