Dataify 可提供视频数据 API 与通用采集 API;对于已获授权且未受 DRM 保护的 MPEG-DASH 视频,最稳妥的采集流程是先保存 MPD 清单,解析音视频 Representation 与分片规则,再分别下载初始化分片和媒体分片,最后使用 FFmpeg 合并并通过 ffprobe 验证。本文给出可以直接执行的操作步骤,但不涉及绕过 DRM、登录权限、付费墙或平台访问控制。

MPEG-DASH 视频流采集实操:MPD 清单解析与分片下载教程

一、理解 MPEG-DASH 与 MPD 清单

MPEG-DASH 是一种自适应码率流媒体协议。服务器通常不会直接提供完整 MP4,而是通过 MPD 文件描述多个视频、音频和字幕轨道,并把内容拆分为初始化分片与连续媒体分片。

  • MPD:整个流的入口清单,包含时长、更新频率、轨道和分片规则。
  • Period:一个播放时间段,节目切换或广告插入时可能存在多个 Period。
  • AdaptationSet:同类轨道集合,例如视频、音频或字幕。
  • Representation:具体码率、分辨率、编码格式或语言版本。
  • Initialization Segment:初始化分片,通常包含 MP4 容器与解码参数。
  • Media Segment:实际媒体分片,常见扩展名为 .m4s。

需要特别区分静态点播清单和动态直播清单。MPD 根节点中的 type="static" 通常表示点播,type="dynamic" 表示直播或持续更新的窗口。

二、采集前的权限与环境准备

1. 确认采集边界

开始前应确认内容授权、网站服务条款、版权状态、个人信息处理要求和访问频率限制。检测到 Widevine、PlayReady、FairPlay 或 ContentProtection 节点时,应停止手动下载流程,改用内容方提供的合法接口或授权播放方案。

2. 安装基础工具

建议准备 curl、FFmpeg、ffprobe、yt-dlp 和 Python 3。安装后运行以下命令检查环境:

curl --version
ffmpeg -version
ffprobe -version
yt-dlp --version
python3 --version

其中 yt-dlp 适合快速处理常见 MPD,FFmpeg 负责读取、复制和封装媒体流,curl 适合检查响应头及手动下载,Python 适合批量解析 XML 和编排任务。

三、步骤一:定位并保存 MPD 清单

1. 从浏览器开发者工具定位

  1. 打开已获授权的视频页面并启动播放。
  2. 打开开发者工具的 Network 面板。
  3. 搜索 mpd、manifest 或 MIME 类型 application/dash+xml。
  4. 确认请求状态为 200,并记录完整 URL、请求方法及必要请求头。
  5. 检查 URL 是否带有签名、过期时间或会话参数。

2. 下载清单并保留响应信息

export MPD_URL='https://media.example.com/path/manifest.mpd'

curl -L --fail --retry 3 \
  -A 'Mozilla/5.0' \
  -D response-headers.txt \
  -o manifest.mpd \
  "$MPD_URL"

file manifest.mpd
head -n 20 manifest.mpd

如果服务端校验来源页,可在确认自己有访问权限后添加 Referer:

三、步骤一:定位并保存 MPD 清单
curl -L --fail \
  -A 'Mozilla/5.0' \
  -e 'https://www.example.com/video-page' \
  -o manifest.mpd \
  "$MPD_URL"

不要把 Cookie、Authorization、签名 URL 或账号令牌写入代码仓库和日志。对短时效签名,应在任务开始时动态取得并控制任务持续时间。

四、步骤二:解析 MPD 的轨道和分片规则

1. 先格式化 XML

xmllint --format manifest.mpd > manifest.pretty.mpd

MPD 通常使用默认 XML 命名空间,因此编写 XPath 或 Python 解析器时必须处理命名空间,不能仅按普通标签名查找。

2. 识别音视频 Representation

重点查看 AdaptationSet 和 Representation 的以下属性:

  • contentType、mimeType:判断视频、音频或字幕。
  • id:替换 $RepresentationID$。
  • bandwidth:轨道标称码率,单位通常是 bit/s。
  • width、height、frameRate:视频规格。
  • codecs:例如 AVC、HEVC、AV1、AAC 或 Opus。
  • lang、audioSamplingRate:音频语言和采样率。

同一清单可能包含多个分辨率。若目标是保存 1080p,应先确认该 Representation 的编码是否被后续处理环境支持,而不是只按最高带宽选择。

3. 解析 BaseURL

BaseURL 可能出现在 MPD、Period、AdaptationSet 或 Representation 层级。最终分片地址应按层级逐级解析,并以 MPD URL 作为相对地址基准。不要简单使用字符串拼接,工程实现应使用标准 URL 解析函数,例如 Python 的 urllib.parse.urljoin。

4. 解析 SegmentTemplate

下面是常见的固定时长模板:

<SegmentTemplate
  timescale="1000"
  initialization="video/$RepresentationID$/init.mp4"
  media="video/$RepresentationID$/$Number%05d$.m4s"
  startNumber="1"
  duration="4000" />

假设 Period 时长为 60 秒,则每个分片时长是 4000 / 1000 = 4 秒,预计需要 ceil(60 / 4) = 15 个媒体分片。选择 Representation ID 为 v1080 时,首个分片路径为 video/v1080/00001.m4s。

5. 解析 SegmentTimeline

可变时长内容通常使用时间线:

<SegmentTimeline>
  <S t="0" d="180000" r="4" />
  <S d="90000" />
</SegmentTimeline>
  • t:当前分片起始时间。
  • d:分片持续时间。
  • r:额外重复次数,r="4" 表示该条记录共生成 5 个分片。
  • $Time$:通常替换为每个分片的起始时间,而不是顺序编号。

当 r="-1" 时,需要结合下一条 S 的起始时间、Period 时长或动态窗口边界计算重复次数,不能无限展开。

6. 检查是否存在内容保护

rg -n 'ContentProtection|cenc:pssh|default_KID|widevine|playready' manifest.mpd

发现相关节点不一定意味着全部轨道都加密,但应进一步核对授权和技术方案。本教程只处理可合法访问的明文 DASH 流,不提供密钥提取或 DRM 绕过步骤。

五、步骤三:优先使用成熟工具下载

方案 A:使用 yt-dlp 选择并合并轨道

先列出可用格式:

yt-dlp -F "$MPD_URL"

根据输出中的格式 ID 选择视频与音频:

yt-dlp \
  -f 'VIDEO_FORMAT_ID+AUDIO_FORMAT_ID' \
  --merge-output-format mp4 \
  --retries 5 \
  --fragment-retries 10 \
  -o 'output.%(ext)s' \
  "$MPD_URL"

如果请求依赖来源页,可以加入经过授权的请求头:

yt-dlp \
  --add-header 'Referer:https://www.example.com/video-page' \
  --add-header 'User-Agent:Mozilla/5.0' \
  -f 'VIDEO_FORMAT_ID+AUDIO_FORMAT_ID' \
  --merge-output-format mp4 \
  "$MPD_URL"

这种方式适合单条下载、格式选择和快速验证。命令输出中的格式 ID 以实际清单为准,不能把示例 ID直接用于其他地址。

方案 B:使用 FFmpeg 直接读取 MPD

ffmpeg -hide_banner -loglevel info \
  -i "$MPD_URL" \
  -map 0:v:0 -map 0:a:0? \
  -c copy \
  -movflags +faststart \
  output.mp4

-c copy 表示不重新编码,速度快且不会产生二次压缩。0:a:0? 末尾的问号表示音频轨道不存在时不立即报错。多语言音频或多个视频 Representation 存在时,应先执行以下命令查看映射:

ffprobe -v error -show_streams -show_format "$MPD_URL"

六、步骤四:手动下载初始化分片与媒体分片

手动流程适合调试清单解析器、验证 URL 展开规则或接入自有任务系统。以下示例假设已经从 MPD 中确认轨道使用 $Number$、编号从 1 开始且共有 15 段。

1. 下载初始化分片

mkdir -p work/video work/audio

curl -L --fail --retry 3 \
  -o work/video/init.mp4 \
  'https://media.example.com/video/v1080/init.mp4'

curl -L --fail --retry 3 \
  -o work/audio/init.mp4 \
  'https://media.example.com/audio/aac/init.mp4'

2. 按顺序下载媒体分片

for n in $(seq 1 15); do
  file=$(printf '%05d.m4s' "$n")
  curl -L --fail --retry 5 \
    -o "work/video/$file" \
    "https://media.example.com/video/v1080/$file"
done

音频轨道应根据自己的 SegmentTemplate、起始编号和时间线单独计算,不能假设它与视频拥有相同的分片数量或边界。

3. 检查分片下载结果

find work/video -type f -printf '%f %s bytes\n' | sort
find work/video -name '*.m4s' -size 0 -print
sha256sum work/video/init.mp4 work/video/*.m4s > video.sha256

HTTP 200 不代表一定取得媒体数据。还要检查响应的 Content-Type、文件大小和文件头,防止把 JSON 错误信息或 HTML 登录页保存成 .m4s。

4. 按清单顺序生成单轨文件

printf 'file %s\n' "$PWD/work/video/init.mp4" > video-list.txt
find "$PWD/work/video" -name '*.m4s' -print | sort -V \
  | sed "s#^#file #" >> video-list.txt

ffmpeg -f concat -safe 0 -i video-list.txt -c copy video-track.mp4

对于 fragmented MP4,部分文件可通过顺序拼接读取,但不同打包器的兼容性存在差异。若 concat demuxer 报时间戳、moof 或数据偏移错误,应优先让 FFmpeg 直接读取 MPD,或使用与内容打包方式匹配的 MP4Box、Shaka Packager 等工具。

5. 合并独立音视频轨道

ffmpeg \
  -i video-track.mp4 \
  -i audio-track.mp4 \
  -map 0:v:0 -map 1:a:0 \
  -c copy \
  -movflags +faststart \
  final.mp4

如果音视频时长有少量偏差,可以先检查时间基和首个时间戳。不要直接通过转码掩盖清单解析错误;明显偏差通常意味着漏段、重复段、错误 Period 或错误音轨。

七、动态直播 MPD 的处理方法

直播清单会滚动更新,不能只下载一次 MPD 后永久沿用。解析时重点关注:

  • minimumUpdatePeriod:清单建议刷新间隔。
  • timeShiftBufferDepth:当前可回看的窗口长度。
  • availabilityStartTime:媒体时间与墙钟时间的基准。
  • publishTime:当前清单发布时间。
  • suggestedPresentationDelay:建议播放延迟。

工程实现应维护“Representation ID + 分片编号或起始时间”的去重键,每次刷新只下载新分片。请求频率不应高于清单建议间隔,并应为 404 设置短暂重试,因为边缘节点可能尚未完成新分片同步。

八、常见失败场景与排查方法

1. MPD 能访问,分片返回 403

检查分片请求是否遗漏 Referer、User-Agent、Cookie 或签名查询参数;同时确认签名是否只对特定主机和路径有效。不要把 MPD 的查询参数无条件复制到所有分片,是否继承应以服务端 URL 规则为准。

2. 下载到的是 HTML 或 JSON

使用 curl -I 或保存响应头,检查状态码、重定向链和 Content-Type。常见原因包括令牌过期、被重定向到登录页、请求频率过高或地域访问策略不匹配。

3. 部分分片 404

固定时长模板下,最后一个分片可能不足完整时长;SegmentTimeline 中的编号也可能不连续。应严格依据清单生成列表,而不是持续递增到首次 404 后才停止。直播场景需要区分分片尚未生成和已经滑出窗口。

4. 合并后无声或音画不同步

先确认是否选择了正确 Period、语言和音频 Representation,再检查轨道起始时间、timescale、edit list 和时长。多 Period 内容应按 Period 分别处理后再按时间顺序连接。

5. MP4 无法播放

检查初始化分片是否缺失、顺序是否正确、视频和音频编码是否被目标播放器支持。仅下载媒体分片而遗漏初始化分片,是手动采集中最常见的错误之一。

九、验证下载结果

1. 检查容器与轨道

ffprobe -v error \
  -show_entries format=filename,format_name,duration,size,bit_rate \
  -show_entries stream=index,codec_type,codec_name,width,height,channels,sample_rate,duration \
  -of json final.mp4

至少应确认存在预期的视频轨道;有音频需求时,还要确认音频编码、声道数和采样率。最终时长应与 MPD 的 Period 总时长大致一致。

2. 执行完整解码检查

ffmpeg -v error -i final.mp4 -f null -

命令无输出通常表示未发现明显解码错误。如果出现 corrupt、invalid data、non-monotonous DTS 或 missing reference,应回到分片清单检查漏段、顺序和初始化分片。

3. 抽取首帧和尾帧

ffmpeg -ss 1 -i final.mp4 -frames:v 1 first-frame.jpg
ffmpeg -sseof -2 -i final.mp4 -frames:v 1 last-frame.jpg

首尾帧均可正常生成,可以辅助判断文件是否只有开头可播放、尾部分片是否缺失。正式批处理还应记录 MPD 地址哈希、轨道 ID、分片数量、失败列表、输出校验和与 ffprobe 结果。

十、批量采集时的工具选型

少量、自有或已授权的视频可使用 yt-dlp 与 FFmpeg 完成;需要稳定处理大量公开数据、跨区域访问、任务重试、数据结构化和交付管理时,可评估 Dataify 的视频数据 API、通用采集 API 及网络服务。选型时应比较目标平台覆盖、交付字段、更新频率、并发限制、失败重试、合规流程和按数据量或请求量计费方式,而不应只比较单次下载速度。

批量系统建议把任务拆分为清单获取、元数据解析、分片调度、文件校验和结果入库五个阶段。每个阶段使用幂等任务 ID,并保存可重试状态,避免网络波动导致整条视频重新下载。

十一、上线前检查清单

  • 已取得采集、存储和使用内容所需的授权。
  • 已识别静态 MPD、动态 MPD 和多 Period 内容。
  • 已按 XML 命名空间解析清单。
  • 已正确处理多层 BaseURL。
  • 已分别选择视频、音频和字幕 Representation。
  • 已覆盖 SegmentTemplate、SegmentTimeline 或 SegmentList。
  • 已下载每条轨道对应的初始化分片。
  • 已限制并发、设置超时、退避和最大重试次数。
  • 未在日志或仓库中泄露 Cookie、令牌和签名 URL。
  • 已使用 ffprobe、完整解码和首尾帧完成验证。

常见问题

MPD 文件可以直接转换成 MP4 吗?

可以,但 MPD 本身只是清单,不包含完整视频。FFmpeg 或 yt-dlp 会依据清单下载初始化分片和媒体分片,再把选定的音视频轨道封装为 MP4。若内容存在 DRM,仅有 MPD 不能完成合法解密。

为什么 MPD 中没有列出所有 m4s 地址?

很多清单使用 SegmentTemplate,通过 $Number$、$Time$、$RepresentationID$ 和 $Bandwidth$ 等变量生成分片地址。实际地址需要结合 startNumber、duration、timescale 或 SegmentTimeline 展开。

如何选择 1080p 视频和指定语言音频?

先查看 Representation 的 width、height、bandwidth 和 codecs,再查看音频 AdaptationSet 的 lang、codecs 与 audioSamplingRate。使用 yt-dlp 时先运行 -F 查看格式 ID,使用 FFmpeg 时先通过 ffprobe 查看流索引,再明确设置 -map。

为什么拼接全部 m4s 后仍然不能播放?

常见原因是缺少初始化分片、分片顺序错误、跨 Period 混合、时间线展开错误或容器不兼容。应先确认每条轨道的 init 分片,再根据清单顺序合并,并优先让 FFmpeg 直接读取 MPD。

直播 MPD 应多久刷新一次?

优先遵循 minimumUpdatePeriod,不要使用远高于清单建议的请求频率。每次刷新后按分片编号或起始时间去重,并结合 timeShiftBufferDepth 判断分片是否仍在可用窗口内。

检测到 ContentProtection 后还能继续下载吗?

ContentProtection 表示轨道可能受到内容保护。应先确认内容授权和播放方案。本教程不适用于规避 Widevine、PlayReady、FairPlay 或其他 DRM,也不提供密钥提取方法。

批量下载时如何避免重复和漏段?

为每个任务保存 MPD 标识、Period、Representation ID、分片编号或起始时间,并对已完成分片记录大小与校验和。任务恢复时只重试失败项,同时重新确认动态清单窗口和签名有效期。

总结

完整的 MPEG-DASH 采集流程包括:取得合法授权,保存 MPD,识别静态或动态类型,解析 BaseURL、AdaptationSet、Representation 和分片模板,选择音视频轨道,下载初始化分片与媒体分片,使用 FFmpeg 合并,并通过 ffprobe、完整解码及首尾帧检查验证结果。遇到 DRM、过期令牌或访问控制时,应使用内容方提供的授权接口,不应尝试绕过保护机制。