1. 媒体管线到底是什么
定义:把音视频从“数据源”搬到“屏幕与扬声器”的一条分阶段流水线。它像装配线:每段只做自己的事,并把结果交给下一段。
[DataSource/网络/文件/缓存]
↓
[容器解析 Demux / 分段 ChunkSource]
↓
[(可选) DRM 解密 / MediaDrm]
↓
[解码器 Decoder: Video/Audio (MediaCodec)]
↓ ↓
[AV 同步/时钟] [后处理/混音]
↓ ↓
[Surface(视频)] [AudioTrack(音频)]
↓ ↓
SurfaceFlinger AudioFlinger
↓ ↓
屏幕 扬声器
2. Android 里的两层管线
-
应用层(你能控制):ExoPlayer/Media3、DataSource、Extractor/ChunkSource、DrmSession、MediaCodec*Renderer、AudioTrack、Surface。
-
系统层(你可观测):SurfaceFlinger(把各个图层合成到屏幕)、AudioFlinger(混音/输出)。
视频最终渲染到一个 Surface(SurfaceView/TextureView);音频输出经 AudioTrack 到 AudioFlinger。
3. 各阶段详解(做什么 & 关键点)
3.1 数据入口(DataSource / Cache)
-
负责 HTTP/文件读取、重试、Range 续传、限速、缓存。
-
点播:可用 SimpleCache 落地块级缓存;
直播:缓存小、重在及时性与续拉策略。
3.2 容器解析 / 分段(Demux / ChunkSource)
-
Progressive(单文件) :Extractor(如 Mp4/Mkv/TsExtractor)把容器拆成音轨/视轨样本。
-
HLS/DASH:ChunkSource 读取清单(m3u8/mpd),拉分片,提供 自适应码率(ABR) 切换。
3.3 DRM 解密(可选)
-
MediaDrm 建会话 → 请求许可证 → 设备上安全解密样本(有的要求 Secure 解码+Secure Surface)。
-
常见:Widevine(L1/L3)、PlayReady、FairPlay、ClearKey(联调)。
3.4 解码(MediaCodec)
-
硬解优先;视频 MediaCodecVideoRenderer,音频 MediaCodecAudioRenderer。
-
视频输出给 Surface;音频输出给 AudioTrack。
-
关注:色彩空间、HDR、分辨率切换(reconfigure)。
3.5 A/V 同步与时钟
-
一般以音频时钟为主:音频连续播放,视频按时间戳投递到 Surface;
-
落后太多会丢帧,领先会等待;直播可用轻微超速追赶(1.01~1.05x)。
3.6 渲染与合成
-
视频:你的 Surface 帧交给 SurfaceFlinger,与其它 UI 图层合成后显示。
-
音频:AudioTrack 交给 AudioFlinger 混音后输出。
4. 点播/直播/低延迟:管线差异
| 场景 | 重点 | 推荐 |
|---|---|---|
| 点播(MP4/MKV/TS) | 起播快、Seek 快、缓存 | ProgressiveMediaSource + Extractor,确保 MP4 moov 在前(faststart) |
| 自适应点播(HLS/DASH) | 码率切换平滑、网络弹性 | HlsMediaSource/DashMediaSource + ABR |
| 直播 | 滑动窗口、追赶、回看 | HLS/DASH + MediaItem.LiveConfiguration |
| 低延迟直播 | 小缓冲、chunked CMAF | LL-HLS/LL-DASH(服务端支持)+ 客户端追赶策略 |
Live 配置示例(Media3) :
val liveCfg = MediaItem.LiveConfiguration.Builder()
.setTargetOffsetMs(3000) // 目标延迟
.setMinOffsetMs(2000)
.setMaxOffsetMs(5000)
.setMaxPlaybackSpeed(1.02f) // 微超速追赶
.build()
5. ExoPlayer/Media3 常用配置(起播与稳态)
5.1 基本搭建
val player = ExoPlayer.Builder(context).build()
val item = MediaItem.Builder()
.setUri(url) // HLS(m3u8)/DASH(mpd)/MP4
// .setMimeType(MimeTypes.APPLICATION_M3U8) // 可显式指定
// .setLiveConfiguration(liveCfg) // 直播时加
// .setDrmConfiguration(drmCfg) // DRM 时加
.build()
player.setMediaItem(item)
player.prepare()
player.play()
5.2 LoadControl(缓冲策略)
val loadControl = DefaultLoadControl.Builder()
.setBufferDurationsMs(
1500, // minBufferMs:小到快起播
5000, // maxBufferMs:稳态缓冲
500, // bufferForPlaybackMs:到 0.5s 即可开播
1000 // bufferForPlaybackAfterRebufferMs:卡后恢复阈值
).build()
val player = ExoPlayer.Builder(context)
.setLoadControl(loadControl)
.build()
5.3 轨道/码率选择
val trackParams = TrackSelectionParameters.Builder(context)
.setMaxVideoSizeSd() // 例如弱网保清晰度
// .setPreferredAudioLanguage("zh")
.build()
player.trackSelectionParameters = trackParams
5.4 DRM
val drmCfg = MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
.setLicenseUri(licenseUrl)
.build()
5.5 缓存
val cache = SimpleCache(cacheDir, LeastRecentlyUsedCacheEvictor(512L * 1024 * 1024))
val dataSource = DefaultHttpDataSource.Factory()
val cacheDataSource = CacheDataSource.Factory()
.setCache(cache)
.setUpstreamDataSourceFactory(dataSource)
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
6. 指标与埋点(一定要量化)
-
启动:DNS/TCP/SSL/清单/首片/解码初始化、首帧时间(First Frame)、起播总时延。
-
稳态:平均/瞬时码率、分辨率切换次数、音画同步误差、丢帧、Audio 失步/underrun、Rebuffer 次数/时长。
-
错误:网络超时、Extractor 错误、DRM 错误(类型/子码)、Codec 错误、Surface 丢失。
-
直播:Live edge 偏差、追赶次数、实际延迟分布。
7. 常见问题 & 对策
| 问题 | 典型原因 | 解决 |
|---|---|---|
| 起播慢 | MP4 moov 在尾、清单/首片拉取慢 | MP4 faststart;HLS/DASH 合理分片(2–6s)与预取 |
| 卡顿/频繁 rebuffer | 缓冲阈值过低、ABR 过激进、网络抖动 | 调整 LoadControl 和 ABR 策略、降初始清晰度、启用预缓存 |
| 音画不同步 | 时钟漂移、音频切出/切入 | 以音频时钟为主;直播用轻微超速追赶 |
| 绿屏/花屏/黑屏 | 解码器不稳、色彩/secure 配置不匹配 | 切换解码器、核对色彩与 HDR、DRM 下使用 Secure Surface |
| 旋转/比例异常 | 容器/流的旋转元数据未处理 | 让播放器跟随元数据旋转;TextureView 用矩阵修正 |
| 切到后台无声/崩 | AudioFocus、AudioAttributes 配置不当 | 正确请求/释放焦点;合理处理 AudioAttributes |
| Surface 销毁 | 生命周期错位 | surfaceDestroyed/TextureDestroyed 时 先 setSurface(null) 再 release |
8. 调试与排障
-
ExoPlayer:AnalyticsListener、DebugViewHelper、EventLogger、player.getVideoFormat()。
-
系统工具:Perfetto/Systrace(Graphics/Audio/CPU 轨道)、dumpsys SurfaceFlinger、dumpsys media.codec、dumpsys audio、adb shell setprop media.metrics.enabled 1。
-
网络:抓包/Har、CDN 日志;HLS/DASH 清单有效性(时移窗口、PART、UTCTiming)。
9. 选型与组合(给决策)
- 只做简单点播:Progressive(MP4 faststart)+ 缓存。
- 弱网/多清晰度:HLS/DASH + ABR。
- 直播/低延迟:LL-HLS/LL-DASH + LiveConfiguration,端云协同。
- 版权内容:CMAF + 多 DRM(WV/PR/FP),Android 走 Widevine(L1/L3 分发策略)。
最小可用模板
val player = ExoPlayer.Builder(context)
.setLoadControl(
DefaultLoadControl.Builder()
.setBufferDurationsMs(1500, 5000, 500, 1000).build()
)
.build()
val item = MediaItem.Builder()
.setUri(url) // m3u8/mpd/mp4 都行
// .setLiveConfiguration(liveCfg) // 直播时打开
// .setDrmConfiguration(drmCfg) // DRM 时打开
.build()
player.setMediaItem(item)
player.prepare()
player.play()