GStreamer DASH Demux 知识文档
基于 gst-plugins-bad/ext/dash 源码整理,聚焦 DASH 协议格式、MPD 解析、dashdemux 播放端全流程
1. 模块概览
dashdemux 是 DASH 播放端的核心元素,继承自 GstAdaptiveDemux,负责:
- 解析 MPD(Media Presentation Description)XML 清单
- 解析 MPD 节点层级(Period → AdaptationSet → Representation → Segment)
- 通过 SegmentTemplate / SegmentList / SegmentBase 三种寻址方式解析片段 URI
- 下载并处理 ISO BMFF(fMP4)片段,含 SIDX 子片段拆分
- 码率自适应切换(Representation 切换)
- 直播流定时刷新与时钟同步(UTCTiming)
- 多 Period 管理
Sink Pad :sink(ALWAYS),接收 application/dash+xml
Src Pad :audio_XX / video_XX / subtitle_XX(SOMETIMES),每种流类型独立输出
2. DASH 协议格式规范
2.1 MPD 文档结构
<MPD>
├── <BaseURL>* --- 基础 URL(可多层)
├── <Location>* --- 替代 MPD 地址
├── <ProgramInformation>* --- 节目信息
├── <UTCTiming>* --- UTC 时钟同步源
├── <Metrics>* --- 度量信息
├── <Period> --- 周期(必须至少一个)
│ ├── <BaseURL>*
│ ├── <SegmentBase> --- 周期级片段寻址
│ ├── <SegmentList>
│ ├── <SegmentTemplate>
│ ├── <AdaptationSet> --- 自适应集
│ │ ├── <ContentComponent>*
│ │ ├── <Accessibility>*
│ │ ├── <Role>*
│ │ ├── <Rating>*
│ │ ├── <Viewpoint>*
│ │ ├── <AudioChannelConfiguration>*
│ │ ├── <ContentProtection>* --- DRM 描述符
│ │ ├── <BaseURL>*
│ │ ├── <SegmentBase>
│ │ ├── <SegmentList>
│ │ ├── <SegmentTemplate>
│ │ ├── <Representation> --- 表示
│ │ │ ├── <BaseURL>*
│ │ │ ├── <SubRepresentation>*
│ │ │ ├── <SegmentBase>
│ │ │ ├── <SegmentList>
│ │ │ └── <SegmentTemplate>
│ │ └── ...
│ └── ...
└── <Period>*
2.1.1 实际 MPD 示例
以下是基于业界广泛使用的 Akamai Big Buck Bunny DASH 测试内容 (dash.akamaized.net) 的真实 MPD 结构,包含视频多码率 + 音频多码率 + SegmentTemplate + SegmentTimeline + UTCTiming 的完整示例:
xml
<?xml version="1.0"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd"
type="static"
profiles="urn:mpeg:dash:profile:isoff-main:2011"
mediaPresentationDuration="PT10M0.000S"
minBufferTime="PT2.000S">
<ProgramInformation moreInformationURL="https://dashif.org/">
<Title>Big Buck Bunny - DASH Test Content</Title>
</ProgramInformation>
<Period id="0" start="PT0.000S">
<!-- ==================== 视频 AdaptationSet ==================== -->
<AdaptationSet contentType="video" segmentAlignment="true"
subsegmentAlignment="true" par="16:9">
<SegmentTemplate timescale="1000"
media="bunny_$RepresentationID$_$Number$.m4s"
initialization="bunny_$RepresentationID$_init.mp4"
startNumber="1">
<SegmentTimeline>
<S t="0" d="2000" r="299"/>
</SegmentTimeline>
</SegmentTemplate>
<Representation id="video_480p" mimeType="video/mp4"
codecs="avc1.64001e" bandwidth="1500000"
width="854" height="480" frameRate="30"
sar="1:1"/>
<Representation id="video_720p" mimeType="video/mp4"
codecs="avc1.64001f" bandwidth="3000000"
width="1280" height="720" frameRate="30"
sar="1:1"/>
<Representation id="video_1080p" mimeType="video/mp4"
codecs="avc1.640028" bandwidth="6000000"
width="1920" height="1080" frameRate="30"
sar="1:1"/>
</AdaptationSet>
<!-- ==================== 音频 AdaptationSet(英语) ==================== -->
<AdaptationSet contentType="audio" lang="en"
segmentAlignment="true" audioSamplingRate="44100">
<AudioChannelConfiguration
schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011"
value="2"/>
<SegmentTemplate timescale="1000"
media="audio_en_$RepresentationID$_$Number$.m4s"
initialization="audio_en_$RepresentationID$_init.mp4"
startNumber="1">
<SegmentTimeline>
<S t="0" d="2000" r="299"/>
</SegmentTimeline>
</SegmentTemplate>
<Representation id="audio_en_128k" mimeType="audio/mp4"
codecs="mp4a.40.2" bandwidth="128000"
audioSamplingRate="44100"/>
<Representation id="audio_en_256k" mimeType="audio/mp4"
codecs="mp4a.40.2" bandwidth="256000"
audioSamplingRate="44100"/>
</AdaptationSet>
<!-- ==================== 音频 AdaptationSet(日语配音) ==================== -->
<AdaptationSet contentType="audio" lang="ja"
segmentAlignment="true" audioSamplingRate="44100">
<AudioChannelConfiguration
schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011"
value="2"/>
<SegmentTemplate timescale="1000"
media="audio_ja_$RepresentationID$_$Number$.m4s"
initialization="audio_ja_$RepresentationID$_init.mp4"
startNumber="1">
<SegmentTimeline>
<S t="0" d="2000" r="299"/>
</SegmentTimeline>
</SegmentTemplate>
<Representation id="audio_ja_128k" mimeType="audio/mp4"
codecs="mp4a.40.2" bandwidth="128000"
audioSamplingRate="44100"/>
</AdaptationSet>
</Period>
</MPD>
要点解读:
| 要素 | 说明 |
|---|---|
type="static" |
点播内容,所有片段在请求时即可用 |
profiles="urn:mpeg:dash:profile:isoff-main:2011" |
ISO BMFF Main profile,支持 SegmentTemplate |
segmentAlignment="true" |
跨 Representation 片段对齐,允许无缝切换 |
par="16:9" |
画面宽高比,GStreamer 用于计算显示尺寸 |
codecs="avc1.64001e" |
H.264 codec OUI,解码器能力匹配依据 |
AudioChannelConfiguration |
声道数声明,GStreamer 据此设置音频 caps |
S t="0" d="2000" r="299" |
300 个片段,每片 2 秒,总时长 600 秒(10 分钟) |
media="bunny_$RepresentationID$_$Number$.m4s" |
URL 模板:如 bunny_video_720p_1.m4s |
initialization="bunny_$RepresentationID$_init.mp4" |
初始化段:如 bunny_video_720p_init.mp4 |
lang="en" / lang="ja" |
多语言轨道,GStreamer 据此创建不同 src pad |
2.1.2 直播 MPD 实际示例
以下是典型 DASH 直播流的 MPD 结构(参考 DASH-IF Live Sim Server 输出):
xml
<?xml version="1.0" encoding="utf-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011"
type="dynamic"
availabilityStartTime="2024-06-15T08:00:00Z"
publishTime="2024-06-15T08:30:42Z"
minimumUpdatePeriod="PT5S"
minBufferTime="PT4S"
timeShiftBufferDepth="PT120S"
suggestedPresentationDelay="PT16S"
maxSegmentDuration="PT2S"
profiles="urn:mpeg:dash:profile:isoff-live:2011">
<UTCTiming schemeIdUri="urn:mpeg:dash:utc:http-xsdate:2014"
value="https://time.akamai.com/?iso"/>
<UTCTiming schemeIdUri="urn:mpeg:dash:utc:http-head:2014"
value="https://time.akamai.com"/>
<Period id="p0" start="PT0S">
<AdaptationSet contentType="video" segmentAlignment="true"
maxWidth="1920" maxHeight="1080"
maxFrameRate="30" par="16:9">
<SegmentTemplate timescale="90000"
media="video_$RepresentationID$_$Time$.m4s"
initialization="video_$RepresentationID$_init.mp4">
<SegmentTimeline>
<S t="1404000000" d="180000" r="5"/>
<S d="180000" r="9"/>
<S d="90000"/>
</SegmentTimeline>
</SegmentTemplate>
<Representation id="v0" bandwidth="800000" width="640" height="360"
frameRate="30" codecs="avc1.64001e"/>
<Representation id="v1" bandwidth="1500000" width="854" height="480"
frameRate="30" codecs="avc1.64001f"/>
<Representation id="v2" bandwidth="3000000" width="1280" height="720"
frameRate="30" codecs="avc1.64001f"/>
<Representation id="v3" bandwidth="6000000" width="1920" height="1080"
frameRate="30" codecs="avc1.640028"/>
</AdaptationSet>
<AdaptationSet contentType="audio" lang="en" audioSamplingRate="48000">
<AudioChannelConfiguration
schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011"
value="2"/>
<SegmentTemplate timescale="48000"
media="audio_$RepresentationID$_$Time$.m4s"
initialization="audio_$RepresentationID$_init.mp4">
<SegmentTimeline>
<S t="748800000" d="96256" r="5"/>
<S d="96256" r="9"/>
<S d="48128"/>
</SegmentTimeline>
</SegmentTemplate>
<Representation id="a0" bandwidth="96000" codecs="mp4a.40.2"/>
<Representation id="a1" bandwidth="128000" codecs="mp4a.40.2"/>
</AdaptationSet>
</Period>
</MPD>
直播 MPD 关键差异:
| 属性 | 点播 vs 直播 | 说明 |
|---|---|---|
type |
static / dynamic |
直播为 dynamic |
availabilityStartTime |
无 / 必需 | 直播必须声明流起始时间 |
minimumUpdatePeriod |
无 / 必需 | 客户端刷新 MPD 的最短间隔 |
timeShiftBufferDepth |
无 / 推荐 | DVR 回看窗口(如 120 秒) |
suggestedPresentationDelay |
无 / 推荐 | 建议延迟(如 16 秒,避免追尾) |
publishTime |
无 / 推荐 | 当前 MPD 发布时间 |
UTCTiming |
可选 / 推荐 | 直播必须同步时钟 |
| SegmentTimeline | 通常 r 固定 |
持续增长,每次刷新追加新 S 元素 |
| URL 模板 | 常用 $Number$ |
直播常用 $Time$(基于时间戳寻址) |
timescale |
视频常用 1000 | 直播视频常用 90000(MPEG-TS 时基),音频用采样率 |
2.2 MPD 根节点属性
| 属性 | 类型 | 说明 |
|---|---|---|
type |
static / dynamic | static=点播,dynamic=直播 |
profiles |
字符串 | DASH 配置文件标识,如 urn:mpeg:dash:profile:isoff-on-demand:2011 |
availabilityStartTime |
dateTime | 直播流开始可用时间(dynamic 必须有) |
availabilityEndTime |
dateTime | 直播流结束时间 |
publishTime |
dateTime | MPD 发布时间 |
mediaPresentationDuration |
duration(ms) | 整体呈现时长 |
minimumUpdatePeriod |
duration(ms) | 直播 MPD 最小刷新间隔 |
minBufferTime |
duration(ms) | 最小缓冲时间 |
timeShiftBufferDepth |
duration(ms) | DVR 回看窗口深度 |
suggestedPresentationDelay |
duration(ms) | 建议的直播延迟 |
maxSegmentDuration |
duration(ms) | 最大片段时长 |
maxSubsegmentDuration |
duration(ms) | 最大子片段时长 |
2.3 Period 属性
| 属性 | 类型 | 说明 |
|---|---|---|
id |
字符串 | 周期标识 |
start |
duration(ms) | 周期起始时间 |
duration |
duration(ms) | 周期时长 |
bitstreamSwitching |
boolean | 是否允许比特流切换 |
2.4 AdaptationSet 属性
| 属性 | 类型 | 说明 |
|---|---|---|
id |
uint | 自适应集 ID |
group |
uint | 分组 |
lang |
字符串 | 语言(BCP47) |
contentType |
字符串 | 内容类型(video/audio/text/...) |
par |
ratio | 宽高比 |
minBandwidth / maxBandwidth |
uint | 带宽范围 |
minWidth / maxWidth |
uint | 宽度范围 |
minHeight / maxHeight |
uint | 高度范围 |
segmentAlignment |
boolean | 片段对齐 |
subsegmentAlignment |
boolean | 子片段对齐 |
2.5 Representation 属性
| 属性 | 类型 | 说明 |
|---|---|---|
id |
字符串 | 表示标识(模板 R e p r e s e n t a t i o n I D RepresentationID RepresentationID 用) |
bandwidth |
uint(bps) | 带宽 |
qualityRanking |
uint | 质量排名 |
dependencyId |
字符串 | 依赖的表示 ID |
2.6 RepresentationBase 共享属性
AdaptationSet / Representation / SubRepresentation 均继承这些属性:
| 属性 | 类型 | 说明 |
|---|---|---|
profiles |
字符串 | 编码配置文件 |
width / height |
uint | 视频分辨率 |
sar |
ratio | 采样宽高比 |
frameRate |
framerate | 帧率 |
minFrameRate / maxFrameRate |
framerate | 帧率范围 |
audioSamplingRate |
字符串 | 音频采样率 |
mimeType |
字符串 | MIME 类型 |
codecs |
字符串 | 编码格式列表 |
startWithSAP |
SAP类型 | 流访问点类型(0-6) |
scanType |
字符串 | 扫描类型 |
2.7 片段寻址方式
DASH 定义三种片段寻址方式,可出现在 Period / AdaptationSet / Representation 任一层级:
SegmentBase
用于单文件点播(ISO BMFF on-demand profile),通过字节范围寻址:
xml
<SegmentBase
timescale="90000"
presentationTimeOffset="0"
indexRange="1234-5678"
indexRangeExact="true">
<Initialization sourceURL="init.mp4" range="0-1233"/>
<RepresentationIndex sourceURL="index.mp4" range="100-200"/>
</SegmentBase>
| 属性 | 说明 |
|---|---|
timescale |
时间刻度(1 秒 = timescale 个单位) |
presentationTimeOffset |
呈现时间偏移 |
indexRange |
SIDX box 的字节范围 |
indexRangeExact |
indexRange 是否精确 |
Initialization |
初始化段 URL + 字节范围 |
RepresentationIndex |
表示索引 URL + 字节范围 |
典型 on-demand 模式:
- 初始化段:
[0, indexRange.first_byte_pos - 1] - SIDX 索引:
indexRange指定的范围 - 媒体数据:SIDX 解析后按子片段字节范围下载
SegmentList
通过 URL 列表逐片段寻址:
xml
<SegmentList timescale="90000" duration="900000" startNumber="1">
<Initialization sourceURL="init.mp4"/>
<SegmentURL media="seg1.m4s" mediaRange="0-999" index="seg1_idx" indexRange="0-99"/>
<SegmentURL media="seg2.m4s"/>
</SegmentList>
| 属性 | 说明 |
|---|---|
duration |
片段时长(timescale 单位) |
startNumber |
起始片段编号 |
SegmentTimeline |
可选时间线(替代固定 duration) |
SegmentURL |
片段 URL + 字节范围 |
完整 MPD 示例:
xml
<MPD type="static" mediaPresentationDuration="PT30S" minBufferTime="PT2S">
<Period>
<AdaptationSet mimeType="video/mp4">
<Representation id="1" bandwidth="1500000">
<SegmentList timescale="90000" duration="900000" startNumber="1">
<Initialization sourceURL="init.mp4"/>
<SegmentURL media="seg1.m4s"/>
<SegmentURL media="seg2.m4s"/>
<SegmentURL media="seg3.m4s"/>
</SegmentList>
</Representation>
</AdaptationSet>
</Period>
</MPD>
SegmentTemplate
通过 URL 模板 + 编号/时间计算寻址:
xml
<SegmentTemplate
timescale="90000"
duration="900000"
startNumber="1"
media="segment_$Number%05d$.m4s"
initialization="init_$RepresentationID$.mp4"
index="index_$Number%05d$.m4x"
bitstreamSwitching="bs_$RepresentationID$.mp4">
<SegmentTimeline>
<S t="0" d="900000" r="9"/>
<S d="450000"/>
</SegmentTimeline>
</SegmentTemplate>
| 属性 | 说明 |
|---|---|
media |
媒体片段 URL 模板 |
initialization |
初始化段 URL 模板 |
index |
索引段 URL 模板 |
bitstreamSwitching |
比特流切换段 URL 模板 |
duration |
固定片段时长(无 SegmentTimeline 时使用) |
startNumber |
起始片段编号 |
SegmentTimeline |
时间线(精确控制每段时长) |
2.8 URL 模板替换规则
模板中 $...$ 标记按以下规则替换:
| 标记 | 替换值 | 格式说明 |
|---|---|---|
$RepresentationID$ |
Representation 的 id 属性 |
字符串,需 URL 安全字符 |
$Number<format>$ |
片段编号 | 默认 %01d,可指定如 %05d |
$Bandwidth<format>$ |
Representation 的 bandwidth |
整数格式 |
$Time<format>$ |
片段起始时间(timescale 单位) | 64 位整数格式 |
$$ |
字面量 $ |
转义 |
示例:
- 模板
segment_$Number%05d$.m4s,Number=3 →segment_00003.m4s - 模板
$RepresentationID$_$Time$.m4s,ID="720p",Time=1800000 →720p_1800000.m4s
2.9 SegmentTimeline 的 S 元素
xml
<SegmentTimeline>
<S t="0" d="900000" r="9"/> <!-- 从 t=0 开始,时长 900000,重复 9 次(共 10 段)-->
<S d="450000"/> <!-- 时长 450000,不重复(1 段)-->
</SegmentTimeline>
| 属性 | 说明 |
|---|---|
t |
起始时间(timescale 单位),仅首个 S 必须指定,后续自动续接 |
d |
时长(timescale 单位),必须 |
r |
重复次数(0=不重复即 1 段,>0=额外重复即 r+1 段,<0=重复至 Period 结束) |
2.10 直播 vs 点播判断
gst_mpd_client_is_live() = (mpd_root_node->type == GST_MPD_FILE_TYPE_DYNAMIC)
| 类型 | MPD type | 特征 |
|---|---|---|
| 点播 (Static) | static(默认) |
固定 MPD,总时长已知 |
| 直播 (Dynamic) | dynamic |
需要 availabilityStartTime,MPD 定期刷新,片段按时可用 |
2.11 片段可用性窗口(Live)
片段可用时间 = availabilityStartTime + period_start + segment_end_time
当前可下载范围 = [availabilityStartTime, now]
可 Seek 范围 = [0, now - AST - maxSegmentDuration]
或 [stop - timeShiftBufferDepth, stop](如有 timeShiftBufferDepth)
2.12 UTCTiming 时钟同步
xml
<UTCTiming schemeIdUri="urn:mpeg:dash:utc:http-xsdate:2014" value="https://time.example.com/xsdate"/>
| schemeIdUri | 方法 | 说明 |
|---|---|---|
urn:mpeg:dash:utc:ntp:2014 |
NTP | 标准 NTP 协议 |
urn:mpeg:dash:utc:sntp:2014 |
SNTP | 简化 NTP |
urn:mpeg:dash:utc:http-head:2014 |
HTTP HEAD | 解析 Date: 响应头 |
urn:mpeg:dash:utc:http-xsdate:2014 |
HTTP XSD | 解析响应体为 XML Schema date |
urn:mpeg:dash:utc:http-iso:2014 |
HTTP ISO | 解析响应体为 ISO 8601 日期 |
urn:mpeg:dash:utc:http-ntp:2014 |
HTTP NTP | 解析 8 字节 NTP 时间戳 |
urn:mpeg:dash:utc:direct:2014 |
直接值 | value 即为 UTC 时间字符串 |
2.13 ContentProtection DRM 描述
xml
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" value="Widevine"/>
<ContentProtection schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95" value="PlayReady">
<cenc:pssh>...</cenc:pssh>
</ContentProtection>
schemeIdUri以urn:uuid:开头 → 识别为特定 DRM 系统- PSSH 数据从子元素提取,通过
GST_EVENT_PROTECTION事件发送到下游
完整 MPD 示例:
xml
<AdaptationSet mimeType="video/mp4" contentType="video">
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"
value="Widevine"/>
<ContentProtection schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95"
value="PlayReady">
<cenc:pssh>AAAAZ3Bzc2g=</cenc:pssh>
</ContentProtection>
<Representation id="1" bandwidth="3000000">
<BaseURL>encrypted_video.mp4</BaseURL>
<SegmentBase indexRange="876-5432">
<Initialization range="0-875"/>
</SegmentBase>
</Representation>
</AdaptationSet>
2.14 DASH Profiles
| Profile | 标识 | 说明 |
|---|---|---|
| ISOFF On-Demand | urn:mpeg:dash:profile:isoff-on-demand:2011 |
单文件 + SIDX,字节范围寻址 |
| ISOFF Main | urn:mpeg:dash:profile:isoff-main:2011 |
主配置文件 |
| ISOFF Live | urn:mpeg:dash:profile:isoff-live:2011 |
SegmentTemplate 直播 |
代码中 profile_isoff_ondemand 标志影响 SIDX 解析和初始化段字节范围计算。
2.15 BaseURL 解析规则
BaseURL 按 MPD → Period → AdaptationSet → Representation 四层逐级拼接:
最终 URL = MPD_base_uri
+ Period.BaseURL[idx]
+ AdaptationSet.BaseURL[idx]
+ Representation.BaseURL[idx]
+ 片段 URI
- 每层可选,缺失则跳过
baseURL_idx默认为 0,用于多 BaseURL 负载均衡- BaseURL 可含查询字符串,提取后存入
queryURL单独附加
2.16 EventStream
EventStream 用于在 MPD 中声明定时事件(广告插入、定时元数据、自定义信令):
xml
<EventStream schemeIdUri="urn:com:example:ad:2019" value="ad-insertion">
<Event id="1" presentationTime="9000000" duration="1800000">
<metadata>...</metadata>
</Event>
<Event id="2" presentationTime="27000000" duration="3600000"/>
</EventStream>
| 属性 | 说明 |
|---|---|
schemeIdUri |
事件方案标识(与 ContentProtection 类似,用 urn:uuid: 标识特定系统) |
value |
方案特定值 |
Event@id |
事件标识 |
Event@presentationTime |
事件呈现时间(timescale 单位,相对于 Period 起始) |
Event@duration |
事件持续时长 |
Event@contentEncoding |
事件内容的编码方式(如 base64) |
GStreamer 处理 :解析 EventStream 和 Event 元素,按 presentationTime 排序存储。播放到对应时间时,通过 GST_EVENT_CUSTOM_DOWNSTREAM 发送事件到下游。
2.17 SupplementalProperty 与 EssentialProperty
这两种描述符出现在 AdaptationSet / Representation 层级,用于声明补充属性或必要条件:
xml
<AdaptationSet contentType="video">
<!-- 补充属性:不影响流选择,提供额外信息 -->
<SupplementalProperty
schemeIdUri="urn:mpeg:dash:adaptation-set-switching:2016"
value="1"/>
<!-- 必要属性:客户端必须识别才能选择此流 -->
<EssentialProperty
schemeIdUri="urn:mpeg:dash:fdh:2018"
value="true"/>
...
</AdaptationSet>
| 描述符 | 语义 | 客户端行为 |
|---|---|---|
SupplementalProperty |
补充信息,可忽略 | 不识别也不影响流选择 |
EssentialProperty |
必要条件,必须识别 | 不识别则应跳过该 AdaptationSet/Representation |
DASH-IF IOP 常用 schemeIdUri:
| schemeIdUri | 用途 |
|---|---|
urn:mpeg:dash:adaptation-set-switching:2016 |
标识可互相切换的 AdaptationSet 组 |
urn:mpeg:dash:fdh:2018 |
Fully Decodable Hierarchical(分层编码必须理解) |
urn:mpeg:dash:picture:audio:2019 |
图片+音频组合流声明 |
GStreamer 处理:解析后存储在 AdaptationSet/Representation 节点上,EssentialProperty 不识别时跳过对应流。
2.18 Role 描述符
Role 描述符声明流的角色(主轨、副轨、评论等),用于客户端的流选择逻辑:
xml
<AdaptationSet contentType="audio" lang="en">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"/>
...
</AdaptationSet>
<AdaptationSet contentType="audio" lang="en">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="commentary"/>
...
</AdaptationSet>
<AdaptationSet contentType="video">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"/>
<Accessibility schemeIdUri="urn:mpeg:dash:role:2011" value="description"/>
...
</AdaptationSet>
DASH Role 值 (urn:mpeg:dash:role:2011):
| value | 说明 |
|---|---|
main |
主轨道(默认选择) |
alternate |
备用轨道 |
supplementary |
补充内容 |
commentary |
评论音轨 |
dub |
配音 |
caption / subtitle |
字幕 |
description |
音频描述(无障碍) |
sign |
手语视频 |
metadata |
元数据轨道 |
enhanced-audio-intelligibility |
增强语音清晰度 |
GStreamer 处理 :解析 Role 值,main 角色的流优先选择。description 标记用于无障碍音频轨道。Role 信息附加到 src pad 的 tag 事件中。
2.19 SubRepresentation
SubRepresentation 声明 Representation 内的子集,用于提取层(scalable coding)或内容拆分:
xml
<Representation id="1080p" bandwidth="6000000" width="1920" height="1080">
<SubRepresentation level="0" dependencyLevel="0" bandwidth="1500000"
contentComponent="1"/>
<SubRepresentation level="1" dependencyLevel="0,1" bandwidth="3000000"
contentComponent="2"/>
<SegmentTemplate .../>
</Representation>
| 属性 | 说明 |
|---|---|
level |
层级编号(SVC/MVC 分层编码) |
dependencyLevel |
依赖的层级 |
contentComponent |
内容组件引用 |
bandwidth |
该子表示的带宽 |
用途:
- 分层编码:H.264 SVC / H.265 SHVC 中,基础层可独立解码,增强层依赖基础层
- 内容组件拆分:一个复用 Representation 拆分为多个逻辑组件
GStreamer 状态 :解析 SubRepresentation 节点属性,但 contentComponent 和 level 在播放流程中未实际使用。
3. MPD 解析流程详解
3.1 MPD XML 解析 (gst_mpdparser_get_mpd_root_node)
输入: MPD XML 原始数据 + 大小
│
├─ libxml2 解析为 DOM 树
├─ 遍历根元素属性:
│ ├─ default_namespace / namespace_xsi / namespace_ext / schemaLocation
│ ├─ id / profiles
│ ├─ type → GST_MPD_FILE_TYPE_STATIC / DYNAMIC
│ ├─ availabilityStartTime / availabilityEndTime / publishTime → GstDateTime
│ ├─ mediaPresentationDuration / minimumUpdatePeriod / minBufferTime
│ ├─ timeShiftBufferDepth / suggestedPresentationDelay
│ └─ maxSegmentDuration / maxSubsegmentDuration
│
├─ 遍历子元素,递归解析各节点:
│ ├─ <BaseURL> → GstMPDBaseURLNode 列表
│ ├─ <Location> → GstMPDLocationNode 列表
│ ├─ <ProgramInformation> → GstMPDProgramInformationNode 列表
│ ├─ <UTCTiming> → GstMPDUTCTimingNode 列表
│ ├─ <Metrics> → GstMPDMetricsNode 列表
│ └─ <Period> → GstMPDPeriodNode 列表
│ └─ 每个 Period 递归解析:
│ ├─ <BaseURL> / <SegmentBase> / <SegmentList> / <SegmentTemplate>
│ └─ <AdaptationSet> → GstMPDAdaptationSetNode
│ ├─ <ContentComponent> / <ContentProtection>
│ ├─ <AudioChannelConfiguration> / <Role> / <Accessibility>
│ ├─ <SegmentBase> / <SegmentList> / <SegmentTemplate>
│ └─ <Representation> → GstMPDRepresentationNode
│ ├─ <SubRepresentation>
│ ├─ <BaseURL>
│ └─ <SegmentBase> / <SegmentList> / <SegmentTemplate>
│ └─ <SegmentTimeline> → GstMPDSegmentTimelineNode
│ └─ <S> → GstMPDSNode (t, d, r)
│
└─ 返回 GstMPDRootNode
3.2 媒体表示设置 (gst_mpd_client_setup_media_presentation)
输入: GstMPDClient + 目标时间/Period索引/Period ID
│
├─ 检查已构建的 periods 是否覆盖目标(快速路径)
│
├─ 否则完全重建 periods 列表:
│ ├─ 清空旧 periods
│ ├─ 遍历 MPD 的 Period 列表:
│ │ ├─ xlink:href 解析(外部 Period 下载)
│ │ ├─ Period 起始时间计算(ISO 23009-1 5.3.2.1):
│ │ │ ├─ period.start 有值 → start = period.start * GST_MSECOND
│ │ │ ├─ 前一 Period duration 有值 → start += prev_duration
│ │ │ ├─ 首个 Period (static) → start = 0
│ │ │ ├─ dynamic → 允许无显式 start
│ │ │ └─ 否则 → "Early Available Period"(跳到 early)
│ │ │
│ │ ├─ Period 时长计算:
│ │ │ ├─ 下一 Period 有 start → duration = next_start - current_start
│ │ │ ├─ period.duration 有值 → duration = period.duration * GST_MSECOND
│ │ │ ├─ dynamic → 允许无显式 duration
│ │ │ └─ 最后 Period + mediaPresentationDuration → duration = total - start
│ │ │
│ │ └─ 创建 GstStreamPeriod 存入列表
│ │
│ └─ 到达目标时间/索引/ID 时停止
│
└─ 返回 TRUE
3.3 流设置 (gst_mpd_client_setup_streaming)
输入: GstMPDClient + GstMPDAdaptationSetNode
│
├─ 创建 GstActiveStream
├─ MIME 类型检测 (gst_mpdparser_representation_get_mimetype)
│ ├─ video/* → GST_STREAM_VIDEO
│ ├─ audio/* → GST_STREAM_AUDIO
│ ├─ application/* → GST_STREAM_APPLICATION
│ └─ 未知 → 返回 FALSE
│
├─ 选择初始 Representation(慢启动策略):
│ └─ 最低带宽 Representation
│ (注释中有最高带宽备选策略,但未启用)
│
├─ 调用 gst_mpd_client_setup_representation() 配置
└─ 追加到 client->active_streams
3.4 Representation 配置 (gst_mpd_client_setup_representation)
输入: GstMPDClient + GstActiveStream + GstMPDRepresentationNode
│
├─ 存储当前 Representation
├─ 清理旧片段列表
│
├─ 计算片段寻址节点(层级查找: Representation > AdaptationSet > Period):
│ ├─ SegmentBase / SegmentList / SegmentTemplate
│ └─ resolveExternal: xlink:href 解析
│
├─ 三大分支构建片段列表:
│
│ ├─ 分支 A: SegmentBase 或 SegmentList
│ │ ├─ 无 SegmentList 但有 SegmentBase → 单文件,整个 Period 为一个片段
│ │ ├─ SegmentList + SegmentTimeline → 遍历 <S> 元素构建 GstMediaSegment
│ │ │ ├─ number 从 startNumber 开始,每个 S 按 r+1 递增
│ │ │ ├─ scale_start 从 S.t 累积,scale_duration = S.d
│ │ │ └─ start/duration 从 timescale 转换为纳秒
│ │ └─ SegmentList 无 SegmentTimeline → 遍历 SegmentURL
│ │ ├─ 每个 SegmentURL 一个 GstMediaSegment
│ │ └─ number 从 startNumber 逐个递增
│ │
│ ├─ 分支 B: SegmentTemplate
│ │ ├─ 有 SegmentTimeline → 同分支 A 的 S 元素遍历
│ │ │ └─ SegmentURL = NULL(按需模板替换生成 URL)
│ │ └─ 无 SegmentTimeline → 不构建片段列表
│ │ └─ 片段按需计算(segment_index + startNumber)
│ │
│ └─ 无任何寻址节点 → 单片段(BaseURL 直接指向文件)
│
├─ Period 边界裁剪: 超出 Period 时长的片段被截断或丢弃
│
├─ BaseURL 解析: 四层拼接 (MPD → Period → AdaptationSet → Representation)
│
└─ presentationTimeOffset 计算:
└─ PTO = presentationTimeOffset * GST_SECOND / timescale
3.5 片段 URI 解析 (gst_mpd_client_get_next_fragment)
输入: GstMPDClient + stream_index
│
├─ 路径 A: 有显式片段列表 (stream->segments != NULL)
│ ├─ 索引 stream->segment_index
│ ├─ SegmentURL 存在:
│ │ ├─ mediaURL = gst_mpdparser_get_mediaURL(stream, SegmentURL)
│ │ └─ indexURL = SegmentURL->index
│ └─ SegmentURL 为 NULL(SegmentTemplate + SegmentTimeline):
│ ├─ mediaURL = build_URL_from_template(template->media, id, number, bandwidth, time)
│ ├─ indexURL = build_URL_from_template(template->index, ...)
│ └─ number/time 根据当前片段的 scale_start/duration 计算
│
├─ 路径 B: 无片段列表(SegmentTemplate 无 SegmentTimeline)
│ ├─ segment_number = segment_index + startNumber
│ ├─ segment_time = segment_index * fragment_duration
│ └─ mediaURL = build_URL_from_template(template->media, id, number, bandwidth, time)
│
└─ URI 最终化:
├─ gst_uri_from_string_with_base(baseURL, mediaURL)
├─ 附加 queryURL
└─ 填充 GstMediaFragmentInfo (uri, range, timestamp, duration, discontinuity)
3.6 初始化段解析 (gst_mpd_client_get_next_header)
输入: GstMPDClient + stream_index
│
├─ SegmentBase 路径:
│ ├─ Initialization 存在 → 获取 URL + 字节范围
│ └─ Initialization 不存在 但 indexRange 存在
│ └─ init 数据 = [0, indexRange.first_byte_pos - 1]
│ (SIDX 前的所有数据即为初始化段)
│
└─ SegmentTemplate 路径:
└─ initialization 模板存在 → build_URL_from_template
(使用 $RepresentationID$, Number=0, Time=0)
4. DASH Demux 播放流程
4.1 初始化
process_manifest()
├─ 创建 GstMPDClient
├─ 存储 mpd_uri / mpd_base_uri
├─ gst_mpd_client_parse() 解析 MPD XML
├─ gst_mpd_client_check_profiles() 检查 profile
├─ gst_mpd_client_fetch_on_load_external_resources() 解析 xlink:onLoad
├─ gst_mpd_client_setup_media_presentation() 构建周期列表
└─ gst_dash_demux_setup_streams() 设置流
│
├─ Live 流:
│ ├─ 检查 availabilityStartTime(必须有)
│ ├─ UTCTiming 时钟同步初始化 + 首次同步
│ ├─ server_now = client_now + clock_compensation
│ ├─ 起始时间 = server_now - suggestedPresentationDelay
│ │ 或 - default_presentation_delay(属性)
│ ├─ 找到目标 Period 索引
│ └─ seek 到起始时间
│
├─ VOD 流:
│ ├─ 从 Period 0 开始
│ └─ seek 到首个片段
│
└─ gst_dash_demux_setup_all_streams()
├─ 遍历所有 AdaptationSet → gst_mpd_client_setup_streaming()
├─ 为每个 ActiveStream:
│ ├─ 创建 src pad (audio_XX / video_XX / subtitle_XX)
│ ├─ 获取输入 caps (video: width/height/framerate; audio: rate/channels)
│ ├─ 提取语言标签
│ ├─ 创建 GstDashDemuxStream
│ ├─ 初始化 SIDX 解析器
│ ├─ ISOBMFF 检测 (video/quicktime, audio/x-m4a)
│ ├─ allow_sidx = profile_isoff_ondemand
│ └─ ContentProtection 事件发送 (PSSH 提取)
└─ trickmode_no_audio 时跳过音频流
4.2 片段下载与处理
stream_update_fragment_info()
├─ gst_mpd_client_get_next_fragment() 获取下一个片段
│ └─ 返回 GstMediaFragmentInfo (uri, range, index_uri, index_range, timestamp, duration)
├─ 设置 fragment.uri / range / duration
├─ 设置 header (init segment): gst_mpd_client_get_next_header()
└─ 设置 index (sidx): gst_mpd_client_get_next_header_index()
start_fragment()
├─ 重置 current_index_header_or_data
└─ 重置 current_offset
└─ ISOBMFF 视频 key-unit trick mode → discont = TRUE
data_received()
├─ 判断当前下载上下文 (index / header / data)
├─ 上下文切换时清空 adapter
├─ 累积数据到 adapter
│
├─ ISOBMFF 流 → gst_dash_demux_handle_isobmff()
│ ├─ 解析 ISOBMFF box (moov, moof, sidx, mdat)
│ ├─ SIDX 完成:
│ │ ├─ 计算 sidx_base_offset
│ │ ├─ 检查 ref_type (不支持 reference_type=1)
│ │ └─ 执行 pending seek 或定位到 entry 0
│ ├─ MOOF 解析:
│ │ ├─ 存储 moof box + offset
│ │ ├─ 查找 sync samples (关键帧表)
│ │ └─ 更新 moof 平均大小统计
│ └─ MDAT 处理:
│ ├─ Key-unit trick mode → 修剪到当前关键帧边界
│ ├─ SIDX 子片段 → 按 SIDX entry 边界切片推送
│ └─ 普通模式 → 推送整个 mdat
│
├─ 非 ISOBMFF + SIDX 完成 → 按 SIDX entry 边界切片
│
└─ 其他 → 全部推送下游
finish_fragment()
├─ ISOBMFF 视频 key-unit trick mode → discont
├─ SIDX 流 + 有 pending seek 或更多子片段 → GST_FLOW_OK
└─ 否则 → gst_adaptive_demux_stream_advance_fragment(duration)
4.3 码率自适应
stream_select_bitrate()
├─ Key-unit trick mode → 不切换(锁定码率)
├─ 视频流: 限制码率 ≤ max_bitrate 属性
├─ 倍速播放: bitrate /= ABS(rate)(更快速度 → 更低质量)
├─ gst_mpd_client_get_rep_idx_with_max_bandwidth()
│ ├─ 遍历所有 Representation
│ ├─ 过滤: bandwidth ≤ max_bandwidth
│ ├─ 过滤: width ≤ max_video_width
│ ├─ 过滤: height ≤ max_video_height
│ ├─ 过滤: framerate ≤ max_video_framerate
│ ├─ 选择: 最高带宽的合格 Representation
│ └─ 无合格 → 最低带宽 Representation(兜底)
│
├─ 选择了不同的 Representation:
│ ├─ gst_mpd_client_setup_representation() 重新配置
│ ├─ 更新 caps
│ ├─ 重置 SIDX 解析器 (allow_sidx = TRUE)
│ ├─ 清空 ISOBMFF 状态、adapter、moof、sync samples
│ └─ 返回 TRUE → 触发流切换
│
└─ 未切换 → 返回 FALSE
4.4 直播刷新
get_manifest_update_interval()
└─ MIN(mpd_root_node->minimumUpdatePeriod * 1000, 30分钟)
update_manifest_data()
├─ 解析新 MPD 到新 GstMPDClient
├─ 转移流位置状态:
│ ├─ 按 Period ID 或索引定位当前周期
│ ├─ 遍历所有活跃流:
│ │ ├─ 从旧客户端获取下一片段时间戳
│ │ └─ 在新客户端 seek 到该时间 (+10μs 补偿 timescale 舍入)
│ └─ 重新进行时钟漂移补偿
└─ 失败 → GST_FLOW_ERROR
stream_get_fragment_waiting_time()
├─ 获取下一片段的可用开始时间
├─ 差值 = availability_start - client_now
└─ 减去 clock_compensation(如果服务器时钟落后,等待更久)
4.5 时钟同步
UTCTiming 时钟同步流程:
│
├─ setup_streams() 中初始化:
│ ├─ 检查 MPD UTCTiming 元素
│ ├─ 选择支持的方法 (优先级: NTP > HTTP HEAD > HTTP XSD > HTTP ISO)
│ └─ 创建 GstDashDemuxClockDrift
│
├─ poll_clock_drift() 定期同步:
│ ├─ NTP: 连接 NTP 服务器,等待同步(5s 超时),读取时间
│ ├─ HTTP HEAD: 获取 URL,解析 Date: 头(RFC 5322)
│ ├─ HTTP XSDATE/ISO: 获取 URL,解析响应体为日期时间
│ ├─ HTTP NTP: 获取 URL,解析 8 字节 NTP 时间戳
│ │
│ ├─ clock_compensation = server_now - client_now
│ │ (client_now = (request_start + request_end) / 2,取中点估计)
│ │
│ └─ 更新间隔: 成功 → 30 分钟,失败/NTP → 30 秒
│
└─ gst_dash_demux_get_server_now_utc()
└─ return client_now + clock_compensation
4.6 Seek
seek()
├─ 解析 seek 事件 (rate, format, flags, start, stop)
├─ 计算 target_pos
├─ 遍历所有 Period 找到包含 target_pos 的 Period
│ ├─ 跨 Period → 释放旧流,设置新 Period,重建所有流
│ └─ 同 Period 但 trickmode_no_audio 变化 → 也重建流
└─ 对每个流调用 stream_seek()
stream_seek()
├─ 清空 adapter、ISOBMFF 状态、moof、sync samples
├─ gst_mpd_client_stream_seek()
│ ├─ 有片段列表 → 线性扫描找到目标片段
│ │ ├─ SNAP_NEAREST: 比较距离,可能前进到下一个边界
│ │ ├─ SNAP_AFTER (forward) / SNAP_BEFORE (reverse)
│ │ └─ 设置 segment_index + segment_repeat_index
│ └─ 模板模式 → index = ts / duration
│
├─ ISOBMFF: 调整时间戳 (offset - period_start)
├─ SIDX 已解析 → gst_dash_demux_stream_sidx_seek()
│ └─ 二分查找 SIDX entry,处理 SNAP 标志
└─ SIDX 未解析 → 存储 pending_seek_ts,等待 SIDX 后延迟 seek
4.7 Period 切换
has_next_period()
└─ Forward: gst_mpd_client_has_next_period()
└─ Reverse: gst_mpd_client_has_previous_period()
advance_period()
├─ Forward: period_idx++
├─ Reverse: period_idx--
├─ gst_dash_demux_setup_all_streams() --- 为新 Period 创建所有流
└─ gst_mpd_client_seek_to_first_segment()
4.8 直播 Seek 范围
get_live_seek_range()
├─ server_now = client_now + clock_compensation
├─ stream_now = server_now - availabilityStartTime
├─ stop = stream_now - maxSegmentDuration (DASH 规范 5.3.9.5.3)
├─ start = 0 (无 timeShiftBufferDepth)
│ 或 stop - timeShiftBufferDepth (有 timeShiftBufferDepth)
└─ 返回 [start, stop]
5. SIDX 解析与子片段拆分
5.1 SIDX 概述
SIDX(Segment Index Box)是 ISO BMFF 中的 sidx box,描述文件内子片段的字节位置和时长。用于 ISOFF on-demand profile,使单文件内容支持部分下载。
5.2 SIDX 解析流程
片段下载流程中的 SIDX 处理:
│
├─ need_header = TRUE → 下载初始化段 [0, indexRange.first-1]
├─ downloading_index = TRUE → 下载 SIDX [indexRange]
│
├─ data_received() 中解析 ISOBMFF box:
│ ├─ 识别 sidx box → 填充 GstSidXParser
│ ├─ SIDX 完成后:
│ │ ├─ sidx_base_offset = current_offset + sidx_box_size + first_offset
│ │ ├─ 检查 ref_type (不支持 =1 的引用类型)
│ │ └─ 执行 pending seek 或定位到 entry 0
│ │
│ └─ 后续数据按 SIDX entry 边界切片
│ ├─ 每个 entry 有 referenced_size(字节大小)
│ ├─ 到达 entry 边界 → 推送当前累积数据,advance subfragment
│ └─ 所有 entry 完成 → finish_fragment
│
└─ SIDX seek (gst_dash_demux_stream_sidx_seek):
├─ 二分查找 entry 包含目标时间戳
├─ SNAP_NEAREST / SNAP_AFTER / SNAP_BEFORE 处理
└─ 设置 entry_index + sidx_position
5.3 SIDX 与片段下载的交互
单文件 on-demand 下载序列:
1. 下载 [0, indexRange.first-1] → 初始化段 (moov)
2. 下载 [indexRange] → SIDX box
3. 解析 SIDX → 得到子片段字节范围列表
4. 按 SIDX entry 顺序下载:
entry[0]: [sidx_base_offset, sidx_base_offset + size0 - 1]
entry[1]: [prev_end + 1, prev_end + size1]
...
5. 每个 entry 完成后推送数据并 advance
6. Key-Unit Trick Mode
6.1 概述
Key-unit trick mode 实现仅下载关键帧的快速浏览模式(快进/快退),适用于 DASH + ISO BMFF 内容。
6.2 实现机制
关键帧 Trick Mode 流程:
│
├─ 触发: trickmode 事件 + allow_trickmode_key_units=TRUE
│
├─ 分块下载 (need_another_chunk):
│ ├─ 初始请求 ~8192 字节 + 平均 moof 大小
│ └─ 可能包含首关键帧数据
│
├─ MOOF 解析时提取关键帧表:
│ ├─ 解析 traf/trun/tfhd box
│ ├─ 构建关键帧偏移表 (moof_sync_samples)
│ ├─ 计算平均关键帧大小和间距
│ └─ 标记首个关键帧是否紧跟 moof
│
├─ 关键帧推进 (advance_sync_sample):
│ ├─ Forward: 移动到下一个关键帧
│ ├─ Reverse: 向前遍历关键帧表
│ └─ 支持按 target_time 跳到目标关键帧
│
├─ 目标时间计算 (get_target_time):
│ ├─ 考虑 QoS 反馈(下游最早时间)
│ ├─ 考虑平均下载时间
│ ├─ 接近下游 → 强制跳更远
│ ├─ 指数移动平均平滑跳过大小
│ └─ 考虑 max_video_framerate 和 max_bitrate 约束
│
└─ 数据路由:
└─ mdat 中仅推送当前关键帧范围的数据
7. CMAF 与分块传输编码
7.1 CMAF 概述
CMAF(Common Media Application Format,ISO 23009-7)是 DASH 和 HLS 共享的媒体片段格式标准,目标是实现一次编码、双协议分发。
核心思想:DASH 和 HLS 传统上使用不同的容器格式(DASH 用 fMP4,HLS 用 MPEG-TS),CMAF 统一采用 ISO BMFF(fMP4)作为唯一容器,使同一套编码输出可同时服务于 DASH MPD 和 HLS M3U8。
CMAF 层级结构
CMAF Switching Set(切换集)
├── 多个 CMAF Track(轨道)--- 同一内容的不同码率/分辨率
│ ├── CMAF Track 由多个 CMAF Segment 组成
│ │ ├── CMAF Header(初始化段)--- moov box
│ │ └── CMAF Fragment(媒体片段)--- moof + mdat
│ │ ├── 完整 CMAF Segment = CMAF Header + 若干 CMAF Fragment
│ │ └── 每个 Fragment 以 SAP(Stream Access Point)开始
│ └── 同一 Switching Set 内的 Track 必须满足:
│ ├── 相同 codec(如都是 H.264)
│ ├── 相同 timescale
│ ├── 片段边界对齐(segmentAlignment=true)
│ └── 可无缝切换
CMAF vs 传统 fMP4
| 特性 | 传统 fMP4 | CMAF fMP4 |
|---|---|---|
| 初始化段 | 自由结构 | 严格规范(moov 仅含初始化元数据) |
| 媒体片段 | moof+mdat | moof+mdat(必须以 SAP 开始) |
| 加密 | 各自定义 | CMAF Encryption(cenc/cbcs 统一) |
| 多轨复用 | 不规范 | CMAF Selection Set(多 Switching Set 组合) |
| 低延迟 | 不支持 | CMAF Chunk(分块传输) |
7.2 CMAF Chunk 与分块传输编码
传统 DASH/HLS 的最小传输单位是完整片段(一个 moof+mdat),客户端必须等整个片段编码+封装完毕才能开始下载。对于 2-6 秒的片段,这意味着至少 2-6 秒的端到端延迟。
CMAF Chunk(分块) 将一个 CMAF Fragment 进一步拆分为多个 CMAF Chunk,每个 Chunk 包含若干已编码的视频帧:
传统方式 --- 完整片段下载:
┌─────────────────────────────────────┐
│ CMAF Fragment (moof + mdat, 2-6s) │ ← 等全部编码完才能下载
└─────────────────────────────────────┘
CMAF Chunk 分块传输:
┌──────────┬──────────┬──────────┬──────┐
│ Chunk 0 │ Chunk 1 │ Chunk 2 │ ... │ ← 编码一部分就推送一部分
│ moof+帧1 │ 帧2-3 │ 帧4-5 │ │
└──────────┴──────────┴──────────┴──────┘
↑ 推送 ↑ 推送 ↑ 推送
t=0 t=0.5s t=1.0s
关键约束:
- Chunk 0 必须包含 moof box(片段元数据)和至少一个关键帧
- 后续 Chunk 只包含 mdat 数据(连续的编码帧)
- 每个 Chunk 边界不要求是 SAP,但 Chunk 0 必须以 SAP 开始
- 客户端需要边接收边解析 ISOBMFF box 结构
7.3 Low-Latency DASH(LL-DASH)
LL-DASH(DASH-IF Low-Latency DASH)基于 CMAF Chunk + HTTP 分块传输编码实现亚秒级延迟:
协议层设计
编码器 CDN 客户端
│ │ │
编码 Chunk 0 ──────►│── HTTP Chunked ───────►│── HTTP Chunked ──────►│
(moof+关键帧) │ Transfer-Encoding │ Transfer-Encoding │ 解析 moof
│ : chunked │ : chunked │ 解码关键帧
编码 Chunk 1 ──────►│── 推送 ───────────────►│── 推送 ──────────────►│ 解码帧
(后续帧) │ │ │
编码 Chunk 2 ──────►│── 推送 ───────────────►│── 推送 ──────────────►│
... │ │ │
│ │ │
端到端延迟 ≈ 编码延迟 + 1 个 Chunk 传输时间 + 解码延迟
≈ 200ms ~ 1s(vs 传统 3-6s)
使用分块传输编码时,服务器不再发送 Content-Length 头部,而是在 HTTP 响应头中加上 Transfer-Encoding: chunked,数据分成一系列小块逐个发送:
GET /video_720p_100.m4s HTTP/1.1
Host: cdn.example.com
────────────────────────────────────────
HTTP/1.1 200 OK
Content-Type: video/mp4
Transfer-Encoding: chunked
1a4c ← 十六进制 chunk 大小 (6,732 字节)
[moof box: 32字节 + traf/trun/tfhd 元数据]
[mdat 前部: 关键帧 NAL 单元数据]
← 空行分隔
0f3a ← 下一个 chunk (3,898 字节)
[mdat 后续: P 帧数据]
← 空行分隔
0a18 ← 下一个 chunk (2,584 字节)
[mdat 后续: 更多帧数据]
← 空行分隔
0 ← 结束标记(0 长度 chunk)
要点:
- 每个 chunk 以十六进制长度行开头,后跟实际数据,空行分隔
- 编码器产出一部分数据就立即以 chunk 推送,无需等整个片段编码完毕
- 客户端边接收边解析 ISOBMFF box 结构(moof → 提取 PTS → 解码关键帧)
- 最后以长度为 0 的 chunk 标记传输结束
MPD 扩展属性
LL-DASH 在 MPD 中通过以下方式声明低延迟能力:
xml
<MPD type="dynamic"
profiles="urn:mpeg:dash:profile:isoff-live:2011,http://dashif.org/guidelines/low-latency-v5"
minimumUpdatePeriod="PT5S"
minBufferTime="PT0.6S"> <!-- 极短缓冲 -->
<Period>
<AdaptationSet contentType="video" segmentAlignment="true">
<SegmentTemplate timescale="90000"
media="chunk_$RepresentationID$_$Number$.m4s"
initialization="init_$RepresentationID$.mp4"
duration="180000"> <!-- 2 秒片段 -->
<SegmentTimeline>
<S t="0" d="180000" r="100"/>
</SegmentTimeline>
</SegmentTemplate>
<Representation id="v1" bandwidth="2000000" width="1280" height="720"
codecs="avc1.64001f"/>
</AdaptationSet>
</Period>
</MPD>
LL-DASH 关键 MPD 特征:
| 特征 | 传统 DASH | LL-DASH |
|---|---|---|
minBufferTime |
2-4 秒 | 0.5-1 秒 |
availabilityStartTime 精度 |
秒级 | 毫秒级 |
| 片段可用性 | 整段可用后 | 逐 Chunk 可用 |
| HTTP 传输 | 普通 GET | Chunked Transfer Encoding |
suggestedPresentationDelay |
10-30 秒 | 1-3 秒 |
| Profile | isoff-live |
isoff-live + low-latency-v5 |
DASH-IF LL-DASH 关键规范点
- AvailabilityStartTime 精确计算 :Chunk 级别的可用时间 =
AST + period_start + chunk_time_offset - 部分片段请求:客户端可在片段未完全生成时发起请求,CDN 通过 chunked transfer 逐块推送
$Time$模板寻址 :推荐使用$Time$而非$Number$,避免编号计算歧义- Resync 机制 :
@maxSegmentDuration+@availabilityStartTime用于客户端重同步
7.4 Low-Latency HLS(LHLS)
LHLS 采用类似思路,通过 M3U8 新增标签实现:
#EXTM3U
#EXT-X-TARGETDURATION:4
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=1.0
#EXT-X-PART-INF:PART-TARGET=0.5
...
#EXT-X-PART:DURATION=0.5,URI="segment1_part1.m4s",INDEPENDENT=YES
#EXT-X-PART:DURATION=0.5,URI="segment1_part2.m4s"
#EXT-X-PART:DURATION=0.5,URI="segment1_part3.m4s"
#EXT-X-PART:DURATION=0.333,URI="segment1_part4.m4s"
#EXTINF:4.0,
segment1.m4s
| LHLS 标签 | 说明 |
|---|---|
#EXT-X-PART-INF |
声明部分片段目标时长 |
#EXT-X-PART |
单个部分片段(= CMAF Chunk) |
#EXT-X-SERVER-CONTROL |
服务器控制参数(CAN-BLOCK-RELOAD、PART-HOLD-BACK) |
INDEPENDENT=YES |
标记该 Part 包含关键帧 |
_HLS_msn / _HLS_part |
阻塞式请求参数,等待特定片段/部分可用 |
7.5 LL-DASH vs LHLS 对比
| 特性 | LL-DASH | LHLS |
|---|---|---|
| 分块单位 | CMAF Chunk | Partial Segment(= CMAF Chunk) |
| 传输方式 | HTTP Chunked Transfer Encoding | HTTP Chunked / 阻塞式请求 |
| 清单声明 | MPD Profile + 短 minBufferTime | #EXT-X-PART-INF + #EXT-X-PART |
| 阻塞请求 | 无(CDN 自动推送 chunked) | _HLS_msn + _HLS_part 参数 |
| 关键帧标记 | CMAF SAP | INDEPENDENT=YES |
| 延迟目标 | < 1 秒 | < 2 秒 |
| CDN 友好度 | 高(标准 HTTP chunked) | 中(需支持阻塞请求) |
| 规范成熟度 | DASH-IF IOP v5+ | HLS RFC 8216bis |
7.6 当前 GStreamer 实现状态
此代码版本不支持 CMAF Chunk 分块传输和低延迟流:
| 功能 | 状态 | 说明 |
|---|---|---|
| CMAF 格式识别 | ❌ | 无 CMAF profile 解析或标识 |
| CMAF Chunk 下载 | ❌ | need_another_chunk 仅用于 trick mode,非低延迟流 |
| HTTP Chunked Transfer | ❌ | UriDownloader 不支持 chunked transfer 编码的流式解析 |
| LL-DASH Profile | ❌ | 不识别 low-latency-v5 profile |
| LHLS Part Segment | ❌ | 不解析 #EXT-X-PART 标签 |
| 阻塞式请求 | ❌ | 不支持 _HLS_msn / _HLS_part 参数 |
| 边收边解 ISOBMFF | ⚠️ 部分 | trick mode 中有 moof 解析能力,但非用于低延迟播放 |
实现低延迟 DASH 需要的核心改动:
- UriDownloader:支持 HTTP Chunked Transfer Encoding,边接收边回调数据
- ISOBMFF 流式解析:在数据不完整时解析 moof/traf/trun,提取 PTS 和解码时间
- MPD 解析:识别 LL-DASH profile,处理 Chunk 级别可用性计算
- 播放引擎:不等整段下载完就推送到解码器
- ABR 策略:基于 Chunk 级别带宽测量而非整段级别
8. 关键设计要点
8.1 直播延迟构成
直播延迟构成:
│
├─ 1. 编码延迟(不可控)
├─ 2. 片段可用性延迟 = maxSegmentDuration
│ └─ 片段完整可用后才能请求
├─ 3. 建议延迟 = suggestedPresentationDelay 或 default_presentation_delay
│ └─ 客户端主动从 live edge 往后退的距离
└─ 4. 时钟偏差补偿
└─ clock_compensation 修正客户端与服务器时间差
实际起始位置 = server_now - suggestedPresentationDelay
9. 与 HLS 的对比
| 特性 | DASH | HLS |
|---|---|---|
| 清单格式 | XML (MPD) | M3U8 文本 |
| 片段寻址 | SegmentTemplate / SegmentList / SegmentBase | EXTINF + URI |
| URL 模板 | $Number$/$Time$/$Bandwidth$/$RepresentationID$ |
无(显式 URI) |
| 自适应集 | AdaptationSet(多表示) | 变体流(多码率) |
| 多周期 | Period(内建支持) | 无内建周期 |
| 直播刷新 | minimumUpdatePeriod | targetduration |
| 时钟同步 | UTCTiming(NTP/HTTP) | 无内建时钟同步 |
| 加密/DRM | ContentProtection(多 DRM 系统) | AES-128(仅一种) |
| 子片段 | SIDX box | 无 |
| 初始化段 | Initialization (SegmentBase/Template) | EXT-X-MAP |
| 时间线 | SegmentTimeline(精确每段时长) | EXTINF(逐段声明) |
| 基类 | GstAdaptiveDemux(共享) | GstAdaptiveDemux(共享) |
| 低延迟 | 无 LHLS 等效 | LHLS(部分片段) |
| MIME 类型 | application/dash+xml |
application/x-hls |
| 字节范围 | indexRange / mediaRange | EXT-X-BYTERANGE |
| 多语言 | AdaptationSet @lang + Role | EXT-X-MEDIA LANGUAGE |
10. 优缺点分析
10.1 MPD 解析模块(gstmpdparser.c / gstmpdclient.c)
优点:
- 节点层次完整:MPD 规范中所有主要元素(Period、AdaptationSet、Representation、Segment*、UTCTiming、ContentProtection)均有对应 GType
- 三种片段寻址方式完整支持:SegmentBase(单文件+SIDX)、SegmentList(URL 列表)、SegmentTemplate(URL 模板)
- SegmentTimeline 精确控制:S 元素的 t/d/r 参数完整解析,支持负数 r(重复至结束)
- URL 模板替换严格遵循规范:
$RepresentationID$、$Number<format>$、$Bandwidth<format>$、$Time<format>$、$$全部支持,含格式校验 - BaseURL 四层拼接:MPD → Period → AdaptationSet → Representation 逐级组合,含查询字符串分离
- 外部资源 (xlink) 解析:
onLoad模式自动下载替换,resolve-to-zero特殊 URI 处理 - Period 时长计算严格遵循 ISO 23009-1 5.3.2.1 规范
缺点:
- XML 解析基于 libxml2 DOM:大 MPD 文件(含大量 S 元素的长直播时间线)全量加载到内存
- 无增量 MPD 更新:每次刷新都完整重新解析,不能只处理变化部分
- xlink
actuate="onRequest"模式未支持:仅处理onLoad urn:mpeg:dash:profile:full:2011等高级 profile 未识别- SubRepresentation 的
contentComponent属性解析后未在播放流程中实际使用 - Metrics / Reporting 节点解析后未在播放逻辑中使用
10.2 DASH Demux 播放流程(gstdashdemux.c)
优点:
- Period 管理完善:跨 Period 自动切换、流重建、时间戳连续性处理
- SIDX 子片段拆分精细:支持 SIDX 解析后按 entry 边界切片、二分查找 seek、延迟 seek(pending_seek_ts)
- ISOBMFF 解析深度:moof/traf/trun/tfhd 解析、关键帧表提取、moof 平均大小统计
- Key-unit trick mode 完整实现:仅下载关键帧、QoS 反馈驱动、目标时间计算、指数移动平均平滑
- 时钟同步丰富:支持 NTP、HTTP HEAD、HTTP XSD/ISO/NTP 多种 UTCTiming 方式
- ContentProtection PSSH 提取:urn:uuid scheme 自动识别并发送 Protection 事件
- Representation 切换约束全面:带宽、分辨率、帧率多维过滤
缺点:
- 无低延迟 DASH 支持:无 Chunked Transfer 编码、无 LTVD (Low-Latency DASH) profile 处理
- DRM 解密未内建:ContentProtection 仅提取 PSSH 发送事件,不做实际解密,依赖下游 cenc 元素
- 字幕支持不完整:APPLICATION 类型流仅在
contains_subtitles时创建 pad,对 WebVTT timed text 等格式处理有限 - trickmode_no_audio 时直接丢弃音频流:而非静音保留流结构,可能影响下游解码器状态
- SIDX 不支持
reference_type=1:遇到引用类型为 1 的 SIDX entry 时无法处理 - Representation 切换时完全重置 ISOBMFF 状态:moof、sync samples、adapter 全部清空,切换后首个 buffer 可能延迟
10.3 码率自适应算法
优点:
- 多维约束选择:带宽 + 分辨率 + 帧率同时过滤,防止选择解码器不支持的 Representation
- 倍速适配:播放速率 > 1.0 时自动降低目标码率(bitrate /= ABS(rate)),保持实时性
- 兜底策略:无合格 Representation 时回退到最低带宽
- max_bitrate 属性限制:可限制视频解码器能力上限
缺点:
- 慢启动策略偏保守:初始选择最低带宽 Representation,首屏画质可能较差
- 无带宽测量与自适应:
stream_select_bitrate仅依赖基类传入的current_download_rate,自身不做带宽估算 - 无码率爬升/下降策略:直接跳到目标 Representation,无渐进试探
- 无防振荡机制:无最小切换间隔、无上升/下降不对称阈值
- Profile 过滤缺失:选择 Representation 时不检查
codecs或profiles兼容性
10.4 直播刷新与时钟同步
优点:
- UTCTiming 多协议支持:NTP / HTTP HEAD / HTTP XSD / HTTP ISO / HTTP NTP 五种方式
- 时钟漂移补偿:
clock_compensation持续修正客户端与服务器时间差 - 请求中点估算:
client_now = (start + end) / 2减少网络延迟误差 - 自动重试:同步失败时缩短更新间隔(30s vs 30min)
缺点:
- 刷新间隔固定为 minimumUpdatePeriod:无法根据网络状况动态调整
- NTP 同步 5 秒超时可能不够:高延迟网络环境下可能频繁超时
- 首次时钟同步阻塞 setup_streams:如果 UTCTiming 服务器响应慢,启动延迟增加
- 无 HTTP Date 头时区偏移处理:依赖 RFC 5322 解析,某些服务器返回非标准格式可能失败
- minimumUpdatePeriod 上限 30 分钟:某些场景可能需要更频繁的刷新
10.5 Seek 处理
优点:
- SIDX 精确 seek:二分查找 + SNAP 标志处理
- 延迟 seek:SIDX 未解析时存储 pending_seek_ts,解析后执行
- 跨 Period seek:自动定位目标 Period 并重建流
- Reverse seek:支持倒放,segment_repeat_index 边界处理
缺点:
- 有片段列表时线性扫描:大型 SegmentTimeline 可能扫描大量 S 元素
- ISOBMFF seek 后 SNAP 标志被剥离:可能导致精确度下降
- Period 切换 seek 时完全重建流:开销较大
- 直播 seek 范围不考虑已下载片段:仅基于 MPD 时长参数计算
10.6 基类架构(GstAdaptiveDemux)
优点:
- 共享框架:HLS/DASH/Smooth Streaming 复用下载循环、ABR、流管理
- header + index + data 三阶段下载:完美适配 DASH 的 init segment + sidx + media 模式
- Preroll 同步暴露:所有流同时就绪才暴露,避免部分流配置
- 每流独立 GstTask:多轨并行下载
need_another_chunk虚函数:支持 DASH 的分块下载(key-unit trick mode)
缺点:
manifest_lock持有时间过长:下载循环持续持有,多流并发可能瓶颈- 下载速率测量在 queue2 之后:受下游反压影响,ABR 估算偏低
download_error_count不区分错误类型:网络临时错误和内容错误同等对待
11. DASH 面试常考知识点
11.1 三种片段寻址方式的区别与适用场景
几乎必问。DASH 定义三种方式,核心区别在于如何定位片段:
| 寻址方式 | 定位方式 | 适用场景 | Profile |
|---|---|---|---|
| SegmentBase | SIDX 字节范围 | 单文件点播(一个 mp4 包含所有内容) | ISOFF On-Demand |
| SegmentList | URL 列表 | 片段数较少的点播 | 任意 |
| SegmentTemplate | URL 模板 + 编号/时间 | 直播 / 大量片段点播 | ISOFF Live / Main |
关键细节:
- SegmentBase 不需要为每个片段生成单独文件,但需要 SIDX box 索引,客户端按字节范围请求
- SegmentTemplate 是唯一支持直播的方式(片段按需生成 URL,无需预知总数)
- 三种方式可出现在 Period / AdaptationSet / Representation 任一层级,子层覆盖父层
- SegmentTemplate + SegmentTimeline 提供最精确的片段时长控制(每段可不同)
11.2 SegmentTimeline S 元素详解
直播场景核心考点:
xml
<SegmentTimeline>
<S t="0" d="900000" r="9"/> <!-- 从 t=0,时长 900000,重复 9 次 → 共 10 段 -->
<S d="450000"/> <!-- 时长 450000,r 默认 0 → 1 段 -->
<S d="900000" r="-1"/> <!-- r=-1:重复直到 Period 结束 -->
</SegmentTimeline>
r 值含义:
r缺省 = 0 → 该条目只代表 1 段r = N (N>0)→ 额外重复 N 次,共 N+1 段r = -1→ 无限重复直到 Period 结束(直播常用)
t 值规则:
- 仅首个 S 必须指定
t - 后续 S 的
t自动 = 前一个 S 的t + d × (r+1) - 直播刷新 MPD 时,SegmentTimeline 追加新 S 元素
11.3 URL 模板 N u m b e r Number Number vs T i m e Time Time
| 标记 | 替换值 | 适用场景 |
|---|---|---|
$Number$ |
片段编号(从 startNumber 开始递增) | 点播 / 固定时长片段 |
$Time$ |
片段起始时间(timescale 单位) | 直播 / 可变时长片段 |
为什么直播推荐 $Time$:
$Number$依赖startNumber和固定duration计算编号,片段时长不均匀时计算出错$Time$直接使用 SegmentTimeline 中的t值,精确对应每段起始时间- 点播中片段时长通常固定,
$Number$足够
格式化 :$Number%05d$ → 5 位零填充;$Time$ → 默认 %01d(64 位整数)
11.4 直播 vs 点播关键参数
| 参数 | 点播 (static) | 直播 (dynamic) | 说明 |
|---|---|---|---|
type |
static |
dynamic |
必问:区分点播/直播 |
availabilityStartTime |
不需要 | 必须 | 流开始可用的时间点 |
minimumUpdatePeriod |
不需要 | 推荐 | 客户端刷新 MPD 的最短间隔 |
timeShiftBufferDepth |
不需要 | 推荐 | DVR 回看窗口(如 120 秒) |
suggestedPresentationDelay |
不需要 | 推荐 | 建议从 live edge 后退的距离 |
publishTime |
不需要 | 推荐 | MPD 发布时间,用于判断时效性 |
| MPD 刷新 | 不需要 | 周期性 | 按 minimumUpdatePeriod 刷新 |
直播延迟构成(常考推导):
总延迟 ≈ 编码延迟 + maxSegmentDuration + suggestedPresentationDelay + clock_compensation
maxSegmentDuration:片段必须完整可用才能请求suggestedPresentationDelay:客户端主动后退,避免追到 live edge 导致缓冲clock_compensation:客户端与服务器时钟偏差
11.5 BaseURL 四层拼接
拼接顺序:MPD → Period → AdaptationSet → Representation → 片段 URI
最终 URL = resolve(MPD_base_uri,
resolve(Period.BaseURL[0],
resolve(AdaptationSet.BaseURL[0],
resolve(Representation.BaseURL[0],
fragment_uri))))
常考陷阱:
- 每层可选,缺失则跳过(不是每层必须有 BaseURL)
- BaseURL 含查询字符串时,提取后存入
queryURL单独附加(不是直接拼接) - 多 BaseURL 用于负载均衡(CDN 故障转移),
baseURL_idx默认 0 - BaseURL 可以是相对路径(相对于上一层解析)或绝对路径
11.6 ABR 码率自适应策略
选择 Representation 的约束维度:
bandwidth≤ 当前可用带宽width≤ max_video_width(解码器能力)height≤ max_video_heightframerate≤ max_video_framerate- 倍速播放时:
bitrate /= ABS(rate)(更快速度 → 更低质量)
常见 ABR 策略对比:
| 策略 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 基于带宽 | 测量下载速率,选择 ≤ 带宽的最高码率 | 简单直接 | 测量有滞后 |
| 基于缓冲 | 缓冲充足时升级,不足时降级 | 平滑 | 反应慢 |
| 混合策略 | 带宽 + 缓冲联合决策 | 稳定 | 复杂度高 |
防振荡机制(GStreamer 缺失):
- 最小切换间隔:避免频繁切换
- 上升/下降不对称阈值:升级需更高带宽确认,降级更敏感
- 粘性因子:倾向保持当前码率
11.7 DRM 与 ContentProtection
ContentProtection 识别流程:
schemeIdUri以urn:uuid:开头 → 识别为特定 DRM 系统- 常见 UUID:
- Widevine:
edef8ba9-79d6-4ace-a3c8-27dcd51d21ed - PlayReady:
9a04f079-9840-4286-ab92-e65be0885f95 - FairPlay(HLS):
94ce86fb-07ff-4f43-adb8-93d2fa968ca2
- Widevine:
- 从子元素
cenc:pssh提取 PSSH box 数据 - 发送
GST_EVENT_PROTECTION事件到下游,由解密插件处理
多 DRM 同时声明:一个 AdaptationSet 可包含多个 ContentProtection(Widevine + PlayReady),客户端选择自身支持的 DRM 系统。
11.8 CMAF 与低延迟 DASH
CMAF 核心价值:一次编码、双协议分发(DASH MPD + HLS M3U8 共享同一套 fMP4 片段)
LL-DASH 实现原理:
传统 DASH:等整个片段编码完 → 下载 → 解码(延迟 3-6s)
LL-DASH: 编码器产出 Chunk → HTTP Chunked Transfer 立即推送 → 边收边解(延迟 < 1s)
LL-DASH vs LHLS 对比:
| 特性 | LL-DASH | LHLS |
|---|---|---|
| 分块单位 | CMAF Chunk | Partial Segment (= CMAF Chunk) |
| 传输方式 | HTTP Chunked Transfer Encoding | 阻塞式请求 (_HLS_msn + _HLS_part) |
| 清单声明 | MPD Profile + 短 minBufferTime | #EXT-X-PART-INF + #EXT-X-PART |
| CDN 友好度 | 高(标准 HTTP chunked) | 中(需支持阻塞请求) |
| 延迟目标 | < 1 秒 | < 2 秒 |
11.9 SIDX 子片段拆分
适用场景:ISOFF On-Demand profile,单文件点播
下载序列:
- 下载初始化段
[0, indexRange.first-1]→ moov box - 下载 SIDX 索引
[indexRange]→ sidx box - 解析 SIDX → 得到子片段字节范围列表
- 按 SIDX entry 顺序逐片段下载
SIDX entry 结构 :每个 entry 包含 referenced_size(字节大小)和 subsegment_duration(时长),用于精确定位和 seek。
11.10 UTCTiming 时钟同步
支持的同步方式:
| 方式 | 原理 | 精度 | 适用场景 |
|---|---|---|---|
| NTP/SNTP | 连接 NTP 服务器 | 毫秒级 | 精度要求高 |
| HTTP HEAD | 解析 Date: 响应头 |
秒级 | 最简单 |
| HTTP XSD/ISO | 解析响应体日期 | 秒级 | NTP 不可用时 |
| direct | value 即为 UTC 时间 | 秒级 | 静态偏移 |
clock_compensation 计算:
client_now = (request_start_time + request_end_time) / 2 // 取中点减少网络延迟误差
clock_compensation = server_now - client_now
server_now_actual = client_now + clock_compensation
11.11 ISOBMFF Box 结构与播放流程
DASH 片段基于 ISO Base Media File Format(ISOBMFF),理解 box 结构是排查播放问题的关键:
初始化段(init segment):
┌─────────────────────────────┐
│ ftyp box --- 文件类型声明 │
│ moov box --- 电影元数据 │
│ ├── mvhd --- 电影头(时长等) │
│ ├── trak --- 轨道 │
│ │ ├── tkhd --- 轨道头 │
│ │ ├── mdia --- 媒体 │
│ │ │ ├── mdhd --- 媒体头(timescale)
│ │ │ ├── hdlr --- 处理器类型
│ │ │ └── minf/stbl --- 采样表
│ │ │ ├── stsd --- 采样描述(codec 配置)
│ │ │ ├── stts --- 时间-采样映射
│ │ │ ├── stss --- 关键帧表
│ │ │ └── stsc/stsz/stco --- 采样布局
│ │ └── ...
│ └── ...
└─────────────────────────────┘
媒体片段(media segment):
┌─────────────────────────────┐
│ moof box --- 电影片段元数据 │
│ ├── mfhd --- 片段头(序号) │
│ └── traf --- 轨道片段 │
│ ├── tfhd --- 轨道片段头 │
│ ├── tfdt --- 基础解码时间 │
│ └── trun --- 轨道片段运行 │
│ ├── 样本数 │
│ ├── 样本时长列表 │
│ ├── 样本大小列表 │
│ └── 样本偏移列表 │
├─────────────────────────────┤
│ mdat box --- 媒体数据 │
│ └── 实际编码帧数据 │
└─────────────────────────────┘
SIDX 索引(on-demand profile):
┌─────────────────────────────┐
│ sidx box --- 片段索引 │
│ ├── reference_ID │
│ ├── timescale │
│ ├── earliest_presentation_time
│ ├── first_offset │
│ └── entries[]: │
│ ├── reference_type (0=media, 1=sidx)
│ ├── referenced_size (字节大小)
│ └── subsegment_duration
└─────────────────────────────┘
关键 box 在播放流程中的角色:
| Box | 作用 | 使用场景 |
|---|---|---|
moov |
全局元数据(轨道定义、codec 配置) | 初始化段,只需下载一次 |
moof |
片段元数据(采样布局、时间戳) | 每个媒体片段开头 |
mdat |
实际编码帧数据 | 跟在 moof 后面 |
sidx |
子片段字节索引 | on-demand profile,支持部分下载和精确 seek |
tfdt |
基础解码时间戳 | 计算 PTS,处理 presentationTimeOffset |
trun |
采样布局(时长、大小、偏移) | trick mode 中提取关键帧位置 |
moof+mdat 配对:每个媒体片段由一个 moof(描述数据布局)+ 一个 mdat(实际数据)组成。GStreamer 解析 moof 获取关键帧表和采样偏移,用于 trick mode 和 seek。