播放器数据结构与功能模块(四)Dash 数据基础

MPD

MPD 的全称是 Media Presentation Description(媒体演示描述),它是 MPEG-DASH 协议中的核心清单文件(Manifest),通常以 .mpd 为后缀。

简单来说,MPD 文件就像是一份“播放地图”“索引表”。播放器在开始播放 DASH 流之前,必须首先下载并解析这个 XML 格式的文件,以了解视频的结构、分片位置及播放规则。

DashManifest

分类 关注点 核心代表参数 参数含义
时钟基准 绝对时间同步 availabilityStartTimeMs 媒体在服务器开始生效的 Unix 时间戳,是直播分片可用性的基准点。
utcTiming 用于同步服务器与客户端时间的 UTC 时间源信息,确保分片定位准确。
publishTimeMs MPD 文件的发布时间,用于校验清单的时效性,防止加载过期索引。
流控策略 刷新与回看 dynamic 标志位,true 代表直播(动态),false 代表点播(静态)。
minUpdatePeriodMs 直播流中,客户端重新拉取并更新 MPD 文件的最小间隔周期。
timeShiftBufferDepthMs 时移缓冲区深度,决定了用户可以向后 Seek(回看)的最大时长。
体验优化 延迟与稳定 suggestedPresentationDelayMs 服务器建议的播放延迟,用于平衡直播的实时性与播放稳定性。
minBufferTimeMs 开始播放前必须维持的最小缓冲时长,用于防止码率切换导致卡顿。
serviceDescription 包含服务描述信息,如低延迟(L-DASH)的特定目标延迟设置。
内容描述 结构与信息 periods 包含的 Period 列表,管理如广告与正片衔接的时间段结构。
durationMs 媒体总时长,点播时固定,直播且未知时设为 C.TIME_UNSET
programInformation 描述性元数据,如节目标题、版权、来源等展示信息。
路径解析 资源寻址 location MPD 文件的 Base URI,用于解析清单中所有分片的相对路径。

DASH 播放的第一步是解析 MPD (Media Presentation Description) 文件。你需要理解 ExoPlayer 是如何将 XML 映射为内存对象 DashManifest 的。

  • 核心类: DashManifestParser

  • 学习重点:

    • 层级结构: 理解 Period -> AdaptationSet -> Representation 的嵌套关系。

      • DashManifest (整本书)

      • Period (章节)

      • AdaptationSet (媒体组,如“视频组”)

      • Representation (具体的规格,如“1080P 视频”)

    • 索引机制: 重点看 SegmentBaseSegmentListSegmentTemplate。DASH 的核心就是如何根据时间戳计算出对应的 URL 分片地址。

    • 动态更新: 对于直播流,研究 DashManifest 是如何定期刷新并处理 publishTime 的。

Period

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @param id period 标识符。可以为 null。
* @param startMs period 的开始时间(毫秒)。
* @param adaptationSets 属于该 period 的 adaptation sets。
* @param eventStreams 属于该 period 的 {@link EventStream}。
* @param assetIdentifier 此 period 的资产标识符。
*/
public Period(
@Nullable String id,
long startMs,
List<AdaptationSet> adaptationSets,
List<EventStream> eventStreams,
@Nullable Descriptor assetIdentifier) {
this.id = id;
this.startMs = startMs;
this.adaptationSets = Collections.unmodifiableList(adaptationSets);
this.eventStreams = Collections.unmodifiableList(eventStreams);
this.assetIdentifier = assetIdentifier;
}

在 Android Media3 (ExoPlayer) 的 DASH 实现中,**Period** 是一个承上启下的关键数据模型。它直接对应了 DASH 协议标准(ISO/IEC 23009-1)中 MPD 清单文件的 <Period> 标签。

简单来说,如果把整个播放列表看作一本书,DashManifest 是书名,那么 Period 就是书中的一个“章节”

核心职责

Period 封装了一段连续时间内的媒体内容组件

  • 时间定位:通过 startMs 字段,它确定了该章节在整段视频时间轴上的起始位置。
  • 资源组织:它作为一个容器,组织了该时段内所有可用的轨道(如:多语言音频、不同分辨率的视频、字幕)以及事件流。

关键代码字段解析

根据你提供的源码,我们可以看到 Period 包含的三个核心要素:

时间轴锚点 (startMs)
  • 作用:表示该 Period 相对于整个 Manifest 开始的偏移量。
  • 意义:播放器依靠这个值来计算当前播放进度落在哪一个 Period 内。对于多 Period 的流(常见于广告插入场景),这决定了切片(Segments)的时间基准。
媒体集容器 (adaptationSets)
  • 对应 XML<AdaptationSet>
  • 作用:这是 Period 最重要的成员。它按媒体类型(Video/Audio/Text)对资源进行分组。
  • 代码逻辑:源码中的 getAdaptationSetIndex(int type) 允许播放器快速定位某种类型的轨道(例如:寻找视频组)。
动态事件流 (eventStreams)
  • 对应 XML<EventStream>
  • 作用:用于处理该时段内的非媒体数据,例如:在特定时间点触发广告转场、更新清单或显示弹幕消息。
在播放流程中的角色

DashMediaPeriod 的准备过程中,它会获取对应的 Period 对象:

  1. 解析器产物DashManifestParser 解析 MPD 时,会根据内容创建一个 Period 列表存入 DashManifest
  2. 调度依据:当播放到该时段时,DashMediaPeriod 会遍历该 Period 下的 AdaptationSet
  3. 构建流:根据用户选择的轨道(如 1080P),从 AdaptationSet 提取 Representation,进而驱动你之前研究的 ChunkStream 进行数据下载。
常见应用场景
  • **广告插入 (SSAI)**:通常第 1 个 Period 是正片,第 2 个 Period 是广告。每个 Period 拥有独立的编码参数和 URL 逻辑。
  • 直播切边:在直播中,随着时间推移,新的 Period 会被加入 Manifest,而过期的 Period 会被移除。
总结

Period 是 Media3 对 DASH 资源进行时间维度划分的实体。如果你在分析 DashMediaPeriod 的初始化逻辑,Period 类就是它获取音视频轨道配置的“说明书”。

AdaptationSet

  • DashManifest (整本书)
  • Period (章节)
  • AdaptationSet
  • Representation

在 DASH 协议及其 Media3 实现中,**AdaptationSet(媒体集容器)** 是连接“用户意图”与“物理资源”的桥梁。如果说 Period 是书的一个章节,那么 AdaptationSet 就是这一章节里的媒体组,如“视频组”。

以下是针对 AdaptationSet 的深度解析:

核心定义:同质资源的集合

AdaptationSet 封装了一组语义相同但规格不同的媒体资源。

  • 语义相同:例如,一个 AdaptationSet 可能包含该时段内所有的英文音轨,或者所有的 4K/1080P/720P 视频流。
  • 规格不同:虽然内容一致,但它们的码率(Bitrate)、分辨率(Resolution)或编码参数各异,以便播放器进行 ABR(自适应码率)切换。

核心职责:轨道分类与筛选

Period 的源码逻辑中,AdaptationSets 列表承担了以下任务:

  • 按类型分组:它将资源划分为 VIDEOAUDIOTEXT(字幕)等类型。
  • 提供索引入口:源码中的 getAdaptationSetIndex(int type) 是播放器寻找轨道的首选方法。例如,当播放器需要渲染画面时,它会先通过此方法找到类型为 C.TRACK_TYPE_VIDEOAdaptationSet
  • 辅助 ABR 决策:由于同一个 AdaptationSet 下的所有 Representation(具体的规格流)是互斥且可切换的,ABR 算法会在同一个容器内部根据当前带宽选择最优解。

代码与 XML 的对应关系

为了方便你编写专栏,可以将 AdaptationSet 的结构映射如下:

XML 标签层级 Media3 类/字段 说明
<AdaptationSet> AdaptationSet 容器实体
contentType 属性 public final int type 标识是视频、音频还是字幕
<Representation> public final List<Representation> representations 该容器下所有可切换的具体码率流
lang 属性 public final String language 标识语言(如 “en”, “zh”)

为什么它是“最重要”的成员?

Period 包含的各项数据中,AdaptationSet 的重要性体现在逻辑分发上:

用户感知的基础:用户在播放器界面选择“中文字幕”或“高清画质”时,底层逻辑实际上是在特定的 AdaptationSet 中进行筛选或切换。

数据流的起点:你之前研究的 ChunkStream 最终下载哪一个 URL,完全取决于播放器从哪个 AdaptationSet 的哪一个 Representation 中提取了索引信息。

多视角/多音轨支持:如果一个视频有“导演剪辑版音轨”和“普通音轨”,它们会分别存在于两个不同的 AdaptationSet 中,供播放器按需加载。

总结
在你的专栏第一章中,可以强调:**AdaptationSet 是播放器进行“轨道适配(Adaptation)”的最小逻辑边界**。理解了它,就能理解为什么 Media3 能够实现复杂的 ABR 切换逻辑而不会把音频流错切成视频流。

Representation

在 DASH 协议中,Representation 是最小的可播放单位(比如“1080P/5Mbps 的视频流”)。理解它的关键在于弄清楚它如何处理元数据物理分片的关系。

AdaptationSet 包含多个 Representation。ABR(自适应码率)算法的工作就是在同一个组内的不同 Representation 之间来回切换。

重点参数

  • format: 描述了这个流的本质(分辨率、码率、编码格式)。
  • baseUrls: 数据的物理下载起点。
  • initializationUri: 极其重要!它指向媒体的初始化块(如 FMP4 的头信息),没有它,后面的分片无法解码。
  • presentationTimeOffsetUs: 解决时间轴对齐问题的偏移量。

重点处理逻辑

源码中通过 newInstance 静态工厂方法,将 Representation 拆分成了两种完全不同的处理逻辑。这是理解该类的核心难点

SingleSegmentRepresentation (单分片)

  • 场景:整个视频就是一个大文件(例如一个 .mp4 文件)。
  • 逻辑:它没有复杂的分片列表,只有一个 indexUri 指向文件内部的索引表(通常是 sidx 盒子)。
  • 关键点:它通常对应源码中的 SingleSegmentBase

MultiSegmentRepresentation (多分片)

  • 场景:标准的流媒体,视频被切成了无数个 .m4s.ts
  • 逻辑:它自己实现了 DashSegmentIndex 接口!这意味着它直接具备了“查表”能力
  • 关键点:它持有一个 MultiSegmentBase,你可以问它:“第 100 个分片的 URL 是什么?”,它会根据模板或列表算给你。

SegmentBase

它就像是播放器的“导航员”,负责把 MPD 清单里的描述转换成具体的下载地址。

把握类的核心职责:时间与空间的转换

SegmentBase 的本质是处理两个映射:

  • 时间 -> 序号:给定播放到第 10 秒,它是第几个分片?
  • 序号 -> 地址:给定第 5 个分片,它的 URL 是什么?字节范围是多少?

核心字段解释:

  • **timescale**:时间刻度(如 90000)。理解它非常重要,因为 MPD 里的时间单位通常不是秒,而是 tick
  • **presentationTimeOffset**:偏移量,用于对齐不同流(如音视频)的时间轴。
  • **initialization**:指向媒体头信息(如 moov)的 RangedUri

理解三种“寻址模式”(核心子类)

Media3 通过三个子类实现了 DASH 协议中不同的分段寻址方案,这是你学习的重难点:

SingleSegmentBase(单文件模式)
  • 场景:整个视频就是一个大文件。
  • 逻辑:它通过 indexStartindexLength 告诉你索引表(sidx)在哪里。播放器先下载这一小段索引,就知道后续怎么随机访问了。
SegmentList(显式列表模式)
  • 场景:MPD 里一行行写死了所有分片的 URL。
  • 逻辑:最简单。它内部维护了一个 List<RangedUri> mediaSegments,查表即得。
SegmentTemplate(模板化模式)—— 最常用
  • 场景:直播或长视频。
  • 逻辑:不写死 URL,而是给一个模板(如 chunk_$Number$.m4s)。
  • 关键代码:看它的 getSegmentUrl 方法,它会调用 mediaTemplate.buildUri 动态生成字符串。

掌握“动态查表”算法(MultiSegmentBase)

如果说子类负责“地址”,那么父类 MultiSegmentBase 负责“计算”。你需要重点研究这两个方法的逻辑:

  1. **getSegmentNum(timeUs, ...)**:
    • 如果是固定时长:直接用 time / duration(数学除法)。
    • 如果有 segmentTimeline:这代表分片时长不固定,源码中使用了二分查找(Binary Search)
  2. 直播逻辑(First/Available)
    • 通过 availabilityTimeOffsetUsperiodStartUnixTimeUs 计算出当前哪些分片已经“产生”并可以下载了。这是处理直播延时的核心。

跑通一个链路

不要试图记住每个数学公式

  1. 输入:用户点击时间轴上的 T 时刻。
  2. 逻辑 1MultiSegmentBase 通过 getSegmentNum 算出序号 N。
  3. 逻辑 2SegmentTemplate(或 List)通过 getSegmentUrl 算出 RangedUri
  4. 逻辑 3Representation 补全 BaseUrl
  5. 输出:生成一个物理的 Chunk 任务交给网络库。

一句话总结:

SegmentBase 是 DASH 的计算引擎Single 靠索引,List 靠表,Template 靠公式。理解了它,你就理解了播放器是如何在长达数小时的流媒体中实现精准定位的。

,