m3u8视频下载与AES-128解密:ts分片合并全链路实战指南 网页视频的下载和本地化保存是很多做内容归档、离线学习、素材整理的人绕不开的一个需求。但真正动手去做的时候你会发现事情远没有右键另存为那么简单——打开开发者工具看到的不是一整段 mp4 地址而是一个后缀为 m3u8 的索引文件里面密密麻麻列着几百个 ts 分片有些分片还经过了 AES-128 加密直接下载下来根本没法播放。这套流程涉及 m3u8 索引解析、AES-128 解密、ts 分片合并三个核心环节任何一个环节出错都会导致最终文件无法播放或者音画不同步。这篇内容就是把我自己在实际处理这类任务时踩过的坑、验证过的方案、以及每一步背后的原理完整梳理出来适合有一定命令行基础、想搞清楚为什么这样做而不是只会复制粘贴命令的读者。全文围绕 m3u8 定位、AES-128 解密、ts 合并这条全链路展开把每个环节的细节和常见故障都讲透。1. 先搞清楚 m3u8 到底是什么以及它为什么让下载变复杂很多人第一次接触 m3u8 的时候会懵明明浏览器里视频播放得好好的为什么我拿到的地址打开是一堆文本要理解这件事得从流媒体传输的基本逻辑说起。1.1 m3u8 的本质是一份播放清单m3u8 文件本身不是视频它是一个基于 UTF-8 编码的文本播放列表属于 HLSHTTP Live Streaming协议的一部分。你可以把它理解成一份快递清单清单上写着第 1 段在 A 地址、第 2 段在 B 地址、第 3 段在 C 地址播放器拿到这份清单后按顺序把每一段下载下来边下边播。这就是为什么你在浏览器里看视频很流畅但想直接保存却找不到一个完整的视频文件——因为服务器根本就没有提供完整文件它只提供了这份清单和一堆碎片。一个典型的 m3u8 文件长这样#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-KEY:METHODAES-128,URIhttps://example.com/key.key,IV0x00000000000000000000000000000000 #EXTINF:10.000000, segment_000.ts #EXTINF:10.000000, segment_001.ts #EXTINF:10.000000, segment_002.ts #EXT-X-ENDLIST这里面几个关键标签必须认识#EXT-X-TARGETDURATION表示每个分片的最大时长#EXTINF表示下一个分片的实际时长#EXT-X-KEY就是加密信息#EXT-X-ENDLIST表示清单结束说明是点播而非直播。看懂这几个标签你就掌握了整个下载流程的钥匙。1.2 为什么会有 ts 分片这种设计ts 是 MPEG-TSTransport Stream格式的缩写原本是用于数字电视广播的容器格式。它被 HLS 选中的核心原因是容错性强每个 ts 分片都是独立可解码的即使中间某个分片下载失败也不会影响其他分片的播放。这种设计对直播场景特别友好——服务器可以持续不断地生成新的分片客户端只需要不断拉取最新的清单即可。但对下载来说这种设计就带来了麻烦你需要把所有分片按顺序下载、按顺序拼接少一个或者顺序错了视频就会卡顿、花屏甚至完全无法播放。而且分片数量可能非常多一个两小时的视频按每个分片 10 秒计算就是 720 个 ts 文件手动下载显然不现实。1.3 定位 m3u8 地址的几种实用方法在实际操作中第一步永远是找到那个 m3u8 地址。浏览器开发者工具是最直接的手段打开 Network 面板筛选m3u8或者media然后刷新页面开始播放通常就能看到请求。但有几个细节需要注意有些站点会把 m3u8 地址藏在多层跳转后面你看到的可能是一个接口返回的 JSON里面才包含真正的 m3u8 地址。这时候要顺着请求链往上找。有些站点会对 m3u8 请求做 Referer 和 User-Agent 校验直接复制地址到命令行工具里下载会返回 403。解决办法是在请求头里带上正确的 Referer。移动端页面的 m3u8 地址往往和 PC 端不同有时候移动端的限制更少可以尝试切换 User-Agent 来获取。提示定位到 m3u8 地址后先用浏览器直接打开这个地址确认能看到文本内容再继续。如果浏览器都打不开说明地址本身有问题或者需要鉴权。2. AES-128 解密为什么下载下来的 ts 播放不了如果你下载完所有 ts 分片合并之后发现播放器打不开或者只有声音没有画面大概率是因为这些分片是加密的而你下载的是密文。AES-128 是 HLS 中最常见的加密方式理解它的工作原理是解决问题的前提。2.1 AES-128 在 HLS 中的加密逻辑HLS 的 AES-128 加密采用的是 CBC 模式。每个 ts 分片在加密前会被填充到 16 字节的整数倍然后用一个 16 字节的密钥和一个 16 字节的初始向量IV进行加密。解密的时候需要同样的密钥和 IV。密钥从哪里来就在 m3u8 文件的#EXT-X-KEY标签里。URI指向密钥文件的地址IV就是初始向量。如果 m3u8 里没有显式指定 IV那么默认使用分片的序号作为 IV——这一点非常关键很多解密失败就是因为忽略了 IV 的推导规则。密钥文件本身通常是一个 16 字节的二进制文件下载下来用十六进制查看器打开应该能看到 32 个十六进制字符。如果你下载到的密钥文件是文本格式的可能需要先做一次编码转换。2.2 用 ffmpeg 一步到位处理加密流对于大多数情况ffmpeg 可以直接处理加密的 m3u8你不需要手动下载密钥、手动解密。命令大致是这样的ffmpeg -headers Referer: https://example.com/ \ -user_agent Mozilla/5.0 \ -i https://example.com/playlist.m3u8 \ -c copy \ -bsf:a aac_adtstoasc \ output.mp4这条命令的核心在于-c copy它表示直接复制音视频流而不重新编码速度非常快。-bsf:a aac_adtstoasc是音频比特流过滤器用于把 AAC 的 ADTS 头转换为 ASC 格式这是 ts 转 mp4 时经常需要的步骤不加的话可能出现音频无法播放的问题。但 ffmpeg 直接处理并不是万能的。我遇到过几种它搞不定的情况密钥服务器需要特定的请求头才能访问、m3u8 地址是动态生成的只能用一次、分片地址是相对路径且需要特殊拼接。这些情况下就得走手动流程。2.3 手动解密的完整步骤与参数计算手动解密听起来复杂拆开来看其实就几步。假设你已经下载好了所有 ts 分片和密钥文件第一步确认密钥长度。用xxd key.key查看正常的 AES-128 密钥应该是 16 字节。如果长度不对说明下载的不是原始密钥文件。第二步确定 IV。如果 m3u8 里写了 IV直接用如果没写IV 就是分片序号的 16 字节大端表示。比如第 0 个分片的 IV 是00000000000000000000000000000000第 1 个是00000000000000000000000000000001以此类推。第三步用 openssl 解密单个分片验证openssl aes-128-cbc -d -in segment_000.ts -out segment_000_dec.ts \ -K 你的密钥十六进制 -iv 你的IV十六进制这里有个容易踩的坑-K和-iv后面跟的都是十六进制字符串不是文件路径。如果你直接把密钥文件路径填进去会报错。正确的做法是先用xxd -p key.key把密钥转成十六进制字符串。第四步验证解密结果。解密后的 ts 文件应该能被 ffplay 或者 VLC 正常播放。如果播放失败检查密钥和 IV 是否正确或者确认加密模式是不是 CBC极少数情况会用其他模式。2.4 解密环节最常见的三个错误在实际操作中解密失败的原因高度集中我把最常见的三个列出来错误现象根本原因解决办法解密后花屏、绿屏IV 推导错误确认 m3u8 是否显式指定 IV未指定时用分片序号openssl 报 bad decrypt密钥格式错误用 xxd -p 转十六进制确认是 16 字节部分分片正常部分异常分片序号从非 0 开始检查 EXT-X-MEDIA-SEQUENCE 的值IV 要加上这个偏移第三个错误特别隐蔽。有些 m3u8 的#EXT-X-MEDIA-SEQUENCE不是 0这意味着第一个分片的序号不是 0 而是这个值。如果你还用 0 作为起始 IV前面几个分片就会解密失败。这个细节很多教程都不会提但实际项目中经常遇到。3. ts 分片合并顺序、工具与音画同步问题所有分片下载并解密完成后下一步就是把它们合并成一个完整的视频文件。这一步看似简单但顺序错了、工具选错了都会导致前功尽弃。3.1 合并顺序为什么如此重要ts 分片的合并必须严格按照 m3u8 中列出的顺序进行。这不是差不多就行的事情——ts 流内部有连续的时间戳PTS/DTS如果顺序错乱播放器解码时时间戳会跳变表现为画面突然倒退、卡死或者音画不同步。那么顺序从哪里来就是 m3u8 文件里#EXTINF下面那一行行文件名。注意文件名不一定是数字递增的有些站点会用时间戳命名有些会用哈希值。所以千万不要想当然地按文件名排序一定要按 m3u8 里的出现顺序来。一个稳妥的做法是先把 m3u8 里的分片地址提取成一个列表文件grep -v ^# playlist.m3u8 | grep -v ^$ segments.txt然后按这个列表的顺序去下载和合并就能保证顺序正确。3.2 三种合并方式的对比与选择合并 ts 分片有三种主流方式各有适用场景第一种直接用 cat 命令拼接。这是最简单的方式cat segment_*.ts output.ts但这种方式有两个前提分片必须是未加密的且文件名排序必须和实际顺序一致。如果分片是加密的拼接出来的是密文没法播放。如果文件名排序不对顺序就乱了。所以 cat 只适合简单场景。第二种用 ffmpeg 的 concat 协议。先创建一个文件列表file segment_000.ts file segment_001.ts file segment_002.ts然后执行ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mp4这种方式的好处是能处理路径问题而且-c copy不重新编码速度快。-safe 0是必须的否则 ffmpeg 会因为安全策略拒绝处理绝对路径。第三种用 ffmpeg 直接处理 m3u8。如果网络条件允许直接让 ffmpeg 拉取 m3u8 并输出 mp4省去手动下载和合并的步骤。这种方式在前面已经讲过适合能直接访问的情况。三种方式的对比方式优点缺点适用场景cat 拼接最简单、最快无法处理加密、依赖文件名排序未加密且命名规范的分片concat 协议顺序可控、支持重编码需要手动准备列表已下载到本地的分片ffmpeg 直拉一步到位依赖网络和鉴权能直接访问 m3u8 的场景3.3 音画不同步的排查思路合并完成后如果发现音画不同步排查方向有这么几个先确认是不是所有分片都下载完整了。缺分片会导致时间戳断裂播放器为了补偿就会调整同步策略。用文件大小对比是个简单办法——正常情况下相邻分片的大小应该差不多如果某个分片明显偏小很可能下载不完整。再确认合并顺序。把合并后的文件用 ffprobe 看一下时长和 m3u8 里所有#EXTINF累加的时长对比如果差得多说明顺序或者完整性有问题。还有一种情况是原始流本身的问题。有些站点的音视频时间戳基准就不一致这种情况下需要用 ffmpeg 重新封装并做时间戳校正ffmpeg -i output.ts -c copy -video_track_timescale 90000 -async 1 fixed.mp4-async 1会让音频流做拉伸对齐-video_track_timescale统一视频时间基。这两个参数配合使用能解决大部分轻微的音画不同步。4. 全链路实操从零到可播放文件的完整流程前面把三个核心环节拆开讲了这一节把它们串起来走一遍完整流程。我会用一个假设的场景来演示你可以对照自己的实际情况调整。4.1 环境准备与工具清单动手之前先把工具准备好。这套流程需要的工具不多但每个都要确认版本ffmpeg核心工具用于拉流、合并、转封装。建议用较新版本老版本对某些 HLS 特性支持不好。Windows 用户下载 essentials 版本即可Linux 用户直接用包管理器安装。openssl用于手动解密验证。Linux 和 macOS 自带Windows 可以用 Git Bash 里带的版本。curl 或 wget用于下载分片和密钥文件。curl 对自定义请求头支持更好推荐优先用 curl。一个文本编辑器用于查看和编辑 m3u8、生成文件列表。注意ffmpeg 的安装路径最好加入系统环境变量否则每次都要输入完整路径很麻烦。Windows 下安装完成后在命令行输入ffmpeg -version验证能输出版本信息就说明配置成功。4.2 抓取 m3u8 与分片下载脚本假设你已经通过开发者工具拿到了 m3u8 地址并且确认这个地址需要带 Referer 才能访问。第一步是把这个 m3u8 下载到本地curl -H Referer: https://example.com/ \ -H User-Agent: Mozilla/5.0 \ -o playlist.m3u8 \ https://example.com/playlist.m3u8下载完成后打开看看确认里面有#EXTINF和分片地址。如果分片地址是相对路径需要根据 m3u8 的 URL 拼接出完整地址。接下来批量下载分片。写一个简单的循环脚本base_urlhttps://example.com/path/ while read -r line; do case $line in \#*|) continue ;; esac curl -H Referer: https://example.com/ \ -o $line \ ${base_url}${line} done playlist.m3u8这个脚本会跳过注释行和空行只下载真正的分片文件。如果你的分片地址是完整的 URL把${base_url}${line}改成$line即可。下载过程中要注意观察是否有失败的分片。curl 默认失败不会重试建议加上--retry 3参数。下载完成后用ls -la *.ts | wc -l统计数量和 m3u8 里的分片数对比确认没有遗漏。4.3 解密与合并的串联执行如果分片是加密的在合并之前要先解密。手动逐个解密效率太低写个循环key_hex$(xxd -p key.key | tr -d \n) iv_hex00000000000000000000000000000000 for f in *.ts; do openssl aes-128-cbc -d -in $f -out dec_$f \ -K $key_hex -iv $iv_hex done注意这里的 IV 是固定的仅适用于所有分片使用同一个 IV 的情况。如果 IV 随分片序号变化需要在循环里动态计算。计算方法是把分片序号转成 16 字节的十六进制比如序号 5 对应00000000000000000000000000000005。解密完成后生成合并列表并执行合并grep -v ^# playlist.m3u8 | grep -v ^$ | sed s/^/file / filelist.txt ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mp4这里有个细节如果解密后的文件名加了dec_前缀filelist.txt 里的文件名也要对应修改。可以用 sed 批量替换sed -i s/^file /file dec_/ filelist.txt4.4 成品验证与常见故障速查合并完成后别急着删原始文件先用 ffprobe 检查一下成品ffprobe -v error -show_format -show_streams output.mp4重点看几个信息时长是否和预期一致、视频流和音频流是否都存在、编码格式是否正常。如果时长明显偏短说明有分片没合并进去如果没有音频流可能是音频编码不被 mp4 容器支持需要转码。播放验证建议用 VLC 或 PotPlayer这两个播放器对 ts 流的兼容性最好。如果播放器能正常播放且拖动进度条不卡顿基本就没问题了。常见故障速查表故障现象可能原因排查方向无法播放分片未解密检查 m3u8 是否有 EXT-X-KEY 标签只有前几秒能播分片顺序错误核对 filelist.txt 顺序花屏、马赛克分片下载不完整对比文件大小重新下载异常分片音画不同步时间戳问题用 -async 1 重新封装播放器报格式错误容器不兼容尝试输出为 mkv 或重新编码5. 那些教程不会告诉你的实战经验前面讲的都是标准流程但实际操作中总会遇到各种意外。这一节分享几个我在多次处理这类任务后总结出来的经验都是踩过坑才明白的。5.1 关于请求头与鉴权的处理技巧很多站点的 m3u8 和分片请求都需要正确的 Referer 和 User-Agent但具体需要哪些头不同站点不一样。我的做法是先在浏览器开发者工具里找到成功的请求右键选择Copy as cURL然后把这条命令粘贴到终端里执行一次确认能拿到数据。接着从这条命令里提取出所有请求头应用到自己的脚本里。这样做的好处是不会遗漏任何必要的头。有些站点会校验Origin、Accept甚至自定义头光靠猜是很难猜全的。另外要注意 Cookie 的处理如果站点依赖登录态需要把 Cookie 也带上。curl 的-b参数可以指定 Cookie 字符串。还有一个细节部分站点的密钥请求和分片请求需要的请求头不一样。密钥请求可能校验更严格。如果分片能下载但密钥下载失败单独给密钥请求加上完整的请求头试试。5.2 大文件处理时的资源管理一个两小时的视频分片数量可能上千总体积可能好几个 GB。这种情况下有几个资源管理上的注意事项磁盘空间要提前确认。下载分片、解密后的副本、合并后的成品三个阶段都会占用空间峰值可能是最终文件的三倍左右。如果磁盘空间紧张可以在解密后删除原始加密分片合并后删除解密分片。下载并发要控制。虽然并发下载能加快速度但并发太高容易被服务器限流甚至封 IP。我的经验是并发数控制在 4 到 8 之间比较稳妥既能提速又不容易触发风控。用xargs -P可以方便地控制并发cat segments.txt | xargs -P 4 -I {} curl -H Referer: https://example.com/ -O {}内存占用也要注意。用 ffmpeg 合并时如果分片很多concat 协议会一次性读取列表内存占用不大但如果用其他方式把整个文件读进内存处理就可能出问题。所以优先用 ffmpeg 的流式处理方式。5.3 直播流与点播流的处理差异前面讲的流程主要针对点播VOD场景也就是 m3u8 里有#EXT-X-ENDLIST标签的情况。如果是直播流m3u8 会不断更新没有 ENDLIST 标签处理方式完全不同。直播流的下载需要持续轮询 m3u8每次拉取新的分片。ffmpeg 可以直接处理直播流ffmpeg -i https://example.com/live.m3u8 -c copy -t 3600 live_output.mp4-t 3600表示录制 3600 秒后停止。如果不加这个参数ffmpeg 会一直录下去直到手动中断。直播流还有个特点是分片会被服务器定期清理所以下载速度必须跟得上生成速度否则会丢分片。这种情况下建议直接用 ffmpeg 拉流不要手动下载分片因为手动流程的延迟太高很容易跟不上。5.4 批量处理与自动化思路如果你需要经常处理这类任务把流程脚本化会省很多事。我的做法是写一个 shell 脚本接受 m3u8 地址和输出文件名作为参数自动完成下载、解密、合并、验证的全流程。脚本里要处理几个关键点自动检测是否加密检查 m3u8 里有没有 EXT-X-KEY、自动推导 IV有显式 IV 用显式的没有就用序号、自动生成合并列表、自动清理临时文件。这样每次只需要一条命令就能搞定。不过自动化也有边界。遇到需要登录、需要特殊请求头、或者 m3u8 地址是动态生成的情况还是得手动介入。我的建议是把能自动化的部分自动化把需要判断的部分留给自己不要追求 100% 全自动那样反而容易在异常情况下出错。提示脚本里加上错误处理和日志输出每次执行后检查日志确认没有异常。特别是分片下载环节一定要验证下载数量是否和 m3u8 里的分片数一致。6. 关于工具选型与方案取舍的一些个人看法聊完了具体操作最后说说工具和方案选择上的思考。这部分没有标准答案更多是我自己的经验判断。ffmpeg 几乎是这个领域绕不开的工具它的优势在于功能全面、社区活跃、文档丰富。但 ffmpeg 也不是万能的它的错误提示有时候很晦涩遇到问题需要一定的经验才能定位。我的建议是把 ffmpeg 当成主力工具但同时掌握手动流程这样在 ffmpeg 搞不定的时候有备选方案。手动流程的价值在于可控性。每一步你都知道发生了什么出了问题能精确定位到是下载环节、解密环节还是合并环节。而 ffmpeg 一步到位虽然方便但出错时你很难判断问题出在哪。所以我的习惯是先用 ffmpeg 试一次如果成功就省事了如果失败就切换到手动流程逐步排查。关于解密工具openssl 是最通用的选择几乎任何环境都有。但如果你需要处理大量分片openssl 的启动开销会比较明显。这种情况下可以考虑用 Python 的 cryptography 库写个批量解密脚本性能会好很多。不过对于偶尔处理一次的场景openssl 完全够用没必要为了性能去折腾。最后说一个心态上的建议这类任务本质上是在和服务器端的各种限制做博弈遇到失败是常态不要指望一次成功。保持耐心逐步排查把每个环节都验证清楚最终一定能拿到可播放的文件。我在最开始做这类任务的时候一个视频折腾了大半天现在流程熟练了大部分情况十几分钟就能搞定。经验积累的过程本身就是价值。