媒体管线

90 阅读4分钟

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 CMAFLL-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 失步/underrunRebuffer 次数/时长

  • 错误:网络超时、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()