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 视频”)
索引机制: 重点看
SegmentBase、SegmentList和SegmentTemplate。DASH 的核心就是如何根据时间戳计算出对应的 URL 分片地址。动态更新: 对于直播流,研究
DashManifest是如何定期刷新并处理publishTime的。
Period
1 | /** |
在 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 对象:
- 解析器产物:
DashManifestParser解析 MPD 时,会根据内容创建一个Period列表存入DashManifest。 - 调度依据:当播放到该时段时,
DashMediaPeriod会遍历该Period下的AdaptationSet。 - 构建流:根据用户选择的轨道(如 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 列表承担了以下任务:
- 按类型分组:它将资源划分为
VIDEO、AUDIO或TEXT(字幕)等类型。 - 提供索引入口:源码中的
getAdaptationSetIndex(int type)是播放器寻找轨道的首选方法。例如,当播放器需要渲染画面时,它会先通过此方法找到类型为C.TRACK_TYPE_VIDEO的AdaptationSet。 - 辅助 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(单文件模式)
- 场景:整个视频就是一个大文件。
- 逻辑:它通过
indexStart和indexLength告诉你索引表(sidx)在哪里。播放器先下载这一小段索引,就知道后续怎么随机访问了。
SegmentList(显式列表模式)
- 场景:MPD 里一行行写死了所有分片的 URL。
- 逻辑:最简单。它内部维护了一个
List<RangedUri> mediaSegments,查表即得。
SegmentTemplate(模板化模式)—— 最常用
- 场景:直播或长视频。
- 逻辑:不写死 URL,而是给一个模板(如
chunk_$Number$.m4s)。 - 关键代码:看它的
getSegmentUrl方法,它会调用mediaTemplate.buildUri动态生成字符串。
掌握“动态查表”算法(MultiSegmentBase)
如果说子类负责“地址”,那么父类 MultiSegmentBase 负责“计算”。你需要重点研究这两个方法的逻辑:
- **
getSegmentNum(timeUs, ...)**:- 如果是固定时长:直接用
time / duration(数学除法)。 - 如果有
segmentTimeline:这代表分片时长不固定,源码中使用了二分查找(Binary Search)。
- 如果是固定时长:直接用
- 直播逻辑(First/Available):
- 通过
availabilityTimeOffsetUs和periodStartUnixTimeUs计算出当前哪些分片已经“产生”并可以下载了。这是处理直播延时的核心。
- 通过
跑通一个链路
不要试图记住每个数学公式
- 输入:用户点击时间轴上的 T 时刻。
- 逻辑 1:
MultiSegmentBase通过getSegmentNum算出序号 N。 - 逻辑 2:
SegmentTemplate(或List)通过getSegmentUrl算出RangedUri。 - 逻辑 3:
Representation补全BaseUrl。 - 输出:生成一个物理的 Chunk 任务交给网络库。
一句话总结:
SegmentBase 是 DASH 的计算引擎。Single 靠索引,List 靠表,Template 靠公式。理解了它,你就理解了播放器是如何在长达数小时的流媒体中实现精准定位的。