你有没有过这种经历:用浏览器看在线视频,想把某一集缓存下来离线看,结果下载完成后,发现文件夹里躺着一堆.ts文件,每个只有几百 KB,单独拿出来根本没法看,加起来才是一个完整视频。我第一次遇到这事时,第一反应是“下载工具坏了”,后来才知道这其实是 HLS 流媒体协议的标准操作。今天这篇文章就把这件事彻底讲明白:为什么浏览器下载的视频会变成多个 TS 文件,怎么把它们合并并转换为 MP4,以及加密流、字幕、批量处理这些进阶场景怎么处理。
内容不绕弯子,从原理一路讲到命令,中间会穿插不少我踩过的坑。文章适合两类人:一类是手里已经攒了几十个 TS 分片不知道怎么处理的普通用户;另一类是刚接触 ffmpeg、想搞懂背后原理的技术爱好者。不管你是哪种,照着文中方案操作,基本一次就能成功。
1. 为什么浏览器下载的视频会变成一堆 TS 文件
1.1 HLS 切片的底层逻辑
先说一个核心概念:HLS,全称 HTTP Live Streaming。这是一种流媒体传输协议,面世已经十几年了,如今几乎所有主流的视频网站、直播平台都在用它。它的核心思想特别简单:不把整个视频当成一个文件传给你,而是先把视频切成若干个小片段,每个片段通常只有 2 到 10 秒,然后额外生成一个索引文件,也就是我们常说的.m3u8播放列表。
播放器拿到这个 m3u8 后,会按照索引里的顺序逐个去服务器拉取这些小片段,边下边播。你可以把视频想象成是一堆积木,m3u8 就是一张图纸,播放器就是一个拼装工人。它不需要把整盒积木搬回家,只需要在需要的时候按照图纸取用对应的那一块。
所以当你用浏览器观看视频时,浏览器本质上只是在按需请求这些片段,并不会在本地形成一个完整的视频文件。如果你用了某些视频嗅探工具、下载插件,或者直接翻浏览器缓存,得到的自然就是一个接一个的 TS 分片。理解了这一点,你也就明白了:这不是下载错误,而是 HLS 协议的产物。
1.2 TS 格式为什么能直接拼接
TS 是 MPEG-TS 的缩写,全称 Transport Stream,中文叫传输流。这个格式最初是给广播电视设计的,是为了在信号不太稳定的情况下尽量保证画面连续,所以它对“丢包”和“损伤”特别能容忍。它会把音视频数据切成 188 字节的小包,一路连续发出去,接收端只要按顺序接收并解码就可以了。
这个设计恰好非常适合网络流媒体:一个 TS 分片内部包含了独立的视频和音频数据,每一段都可以单独解码播放。换句话说,你手里的每个 TS 小文件,其实都是一个可以独立打开的视频,只是时长只有几秒钟。
而反观 MP4,它非常依赖文件头部的索引信息,一个 MP4 文件如果从中间断开,后半截基本就没法正常播放了。这就是为什么 TS 分片可以按顺序二进制级拼接,而 MP4 不能直接这样操作。当你把几十个 TS 文件按顺序串起来,得到的就是一个接近完整的视频流,后续再封装成 MP4 只是换个“容器”的问题。
1.3 没保留 m3u8 也不慌:先判断分片情况
有些人下载完 TS 分片后,m3u8 文件也顺手留在同一个文件夹里了,这是最理想的情况。因为 m3u8 里记录了分片的准确顺序、时长、加密参数,甚至字幕文件的路径,用它可以省掉很多麻烦。
但如果你像我一样,当时只把 TS 文件拖出来了,没有保存 m3u8,也不用太慌张。你只需要检查一下文件名:很多下载工具会按照000.ts、001.ts、002.ts这种规律自动重命名,这种顺序一般就是播放顺序。但如果文件名是一串哈希值或者时间戳,那你就需要小心了,直接按名称排序很可能排错。这时候建议用播放器随便打开两个相邻文件试试看,或者直接用后面会提到的 ffprobe 检查每个文件的时长信息,再判断顺序。
还有一种常见情况:你是在浏览器缓存文件夹里翻到这些 TS 文件的。这种场景下文件名完全随机,顺序大概率是乱的,我不建议手动去排,效率太低还容易出错。更稳妥的做法是回到视频页面重新抓一次 m3u8 地址,再用工具完整下载,这样得到的文件顺序是可靠的。
2. 动手前的准备工作与工具选型
2.1 盘点你手里的分片文件
合并之前,先花两分钟确认一下你手里的家底。打开装 TS 文件的目录,看看这几点:
- 文件总数和总大小:如果一集视频几百 MB,但 TS 文件却只有几十个,每个十几 MB,那大概是视频被切成了比较大的分片;如果每个 TS 只有几百 KB,那说明切片粒度偏小,数量会很多。
- 文件编码类型:这是最容易被忽略的一点。TS 只是容器,里面的视频编码可能是 H.264,也可能是 H.265,甚至可能是 MPEG-2。音频可能是 AAC,也可能是 AC-3。你可以用 ffprobe 快速查看单个分片的编码信息,指导后续用哪种合并方式。如果全是 H.264 + AAC,恭喜你,这是最省事的情况。
这里有一个特别容易踩的坑:不要看到文件扩展名是.ts就以为它肯定可以被直接拼接。网上确实有一些教程让你直接改扩展名成.mp4,然后告诉我“能播放”。这纯粹是碰运气。TS 和 MP4 的容器结构完全不同,改扩展名只是把“说明书”的封面换成了“小说”,内容本质没变,很多播放器照样打不开。反正我实测下来,大部分情况改完扩展名都无法正常播放,或者只能在个别播放器里勉强解码。
2.2 安装 ffmpeg 并验证基础命令
说到音视频处理,绕不开的工具就是 ffmpeg。它是个开源命令行工具,几乎支持你能想到的所有音视频格式,功能极其强大,但很多新手一看“命令行”三个字就想退缩。我不劝你深入学,只是这个场景真的只需要两条命令,熟练掌握之后受益终身。
安装方式不复杂:
- macOS 用户可以用 Homebrew,执行
brew install ffmpeg; - Linux 各发行版一般用自带的包管理器,比如 Debian/Ubuntu 系执行
sudo apt install ffmpeg; - Windows 用户最省事的方式是下载一个静态编译版本,解压后把 bin 目录加入系统环境变量即可。
装完之后打开终端或命令行,执行ffmpeg -version,能看到版本信息就说明安装成功。如果你的环境输出一堆错误,最常见的其实是 ffmpeg 本身没装好,或者 PATH 没有配好,和你要合并的 TS 文件没关系,先解决环境问题再继续。
2.3 ffmpeg、ffprobe 和 concat 的关系
ffmpeg 不是一个孤立工具,它附带了好几个兄弟命令,其中你一定会用到的就是ffprobe。ffprobe 的作用是“读取媒体文件的元信息”,比如视频编码、音频编码、时长、分辨率、帧率等等。在合并之前用 ffprobe 检查分片文件,可以减少很多失败次数。
我说的检查不仅是看单个文件能播不能播,而是看它的详细参数。举个例子:
ffprobe -v error -show_entries stream=codec_type,codec_name -show_entries format=duration -of default=noprint_wrappers=1 000.ts这个命令会输出这个 TS 文件里的视频流编码、音频流编码,以及整个文件的时长。连续抽查前面、中间、后面几个分片,如果它们的编码参数完全一致,说明这些分片是一个完整视频切出来的,合并基础没问题;如果某个分片的参数突然不一样,比如视频分辨率变了,那很可能是下载时混入了另一个视频的分片,需要删掉。
至于 concat,它是 ffmpeg 提供的“拼接”能力。你可以简单把它理解成:把一堆碎片文件,按照你给的顺序,无缝地连接成一个完整文件。ffmpeg 的 concat 有两种常见写法,一种是concat demuxer,另一种是concat protocol,你不需要立刻分清它们的区别,先记住一点:对付我们已经下载到本地的 TS 文件,用 concat demuxer 是最直观也最稳定的方式,命令里写成-f concat就是了。
3. 三套合并方案,按需选择
3.1 无损合并:concat 模式下的 -c copy
先说我最推荐的方案:无损合并,速度最快,画质零损失。
第一步,生成一个分片列表文件。打开终端,进入 TS 文件所在目录,执行下面这个命令:
for f in $(ls *.ts | sort -V); do echo "file '$PWD/$f'"; done > list.txt解释一下这条命令在做什么:ls *.ts | sort -V会把当前目录下所有.ts文件按自然顺序排列,避免出现10.ts排在2.ts前面的问题;后面的for循环则把每一个文件的完整路径写进list.txt。
list.txt的内容大概长这样:
file '/home/me/videos/000.ts' file '/home/me/videos/001.ts' file '/home/me/videos/002.ts'第二步,用 ffmpeg 合并并输出 MP4:
ffmpeg -f concat -safe 0 -i list.txt -c copy -bsf:a aac_adtstoasc -movflags +faststart output.mp4这个命令里的每个参数都有讲究:
-f concat:告诉 ffmpeg 使用 concat demuxer 读取列表文件;-safe 0:允许列表文件里使用绝对路径,否则相对路径反而容易出错;-c copy:这是无损合并的关键,意思是“直接复制流数据,不重新编码”,速度基本等于硬盘读写速度,而且画面质量完全不变;-bsf:a aac_adtstoasc:这个参数很多人不知道,但它的作用很大。TS 容器里的 AAC 音频是 ADTS 裸流格式,而 MP4 容器期望的是另一种封装结构,这个过滤器负责把音频流转换成 MP4 需要的格式。少了它,你合并出来的 MP4 很可能出现“有画面没声音”的情况;-movflags +faststart:把 MP4 的元数据移到文件开头,这样播放器在打开文件时不需要读完整整个文件才能拖动进度条,对在线播放和本地播放都有好处。
实测下来,一集 40 分钟、几十个 TS 分片、总大小 1GB 左右的视频,这条命令通常十几秒就能跑完,而且输出文件的质量和原视频完全一致。这也是我日常处理大多数视频的首选方案。
3.2 重编码方案:什么时候必须转码
有些情况不能直接用-c copy,比如:
- 合并时 ffmpeg 报错,提示某个分片的编码信息和前面不一致;
- 合并出来的 MP4 在手机或老电视上播放不了;
- 视频编码是 H.265,而你的播放设备不支持硬解;
- 想压缩文件体积,减少存储占用。
这时候就需要“重编码”,也就是让 ffmpeg 重新解码再编码一次,把所有视频统一转成兼容性最好的 H.264 + AAC:
ffmpeg -f concat -safe 0 -i list.txt -c:v libx264 -preset medium -crf 23 -c:a aac -b:a 192k -movflags +faststart output.mp4这段命令同样可以拆开理解:
-c:v libx264:用 H.264 编码器重新编码视频流;-preset medium:压缩速度和质量的折中档,电脑性能一般就用这个,时间充足可以换成slow,画质会略好一点;-crf 23:H.264 的“质量档位”,值越小画质越好、文件越大,23 是大多数压制场景的默认平衡点,追求高质量可以调到 18 到 20;-c:a aac -b:a 192k:把音频转成 AAC,码率 192k 对一般在线视频来说已经足够了。
重编码的代价也很明显:速度慢很多,而且理论上会有轻微的画质损失。所以我的原则是:能用-c copy就坚决不用重编码,只有上面说的几种兼容性场景才考虑转码。有些人一上来就直接压制,结果一小时的长视频跑了二十分钟才完成,其实大部分时候真的没必要。
3.3 不想敲命令怎么办:图形工具兜底
如果看到命令行就头大,那也不是没有别的路。市面上有一些开源的批量下载器,它们自带 m3u8 解析和 TS 合并功能,操作方式基本都是“打开 m3u8 地址,点击下载,自动输出 MP4”。这些工具确实很方便,我偶尔也会用。但它的局限性也很明显:对加密 key 的管理不太透明,处理分片数量巨大的自定义场景时不如 ffmpeg 灵活,偶尔合并出来的文件在特种播放器上会有兼容性问题。
所以我才会建议,哪怕你暂时用着图形工具,也值得抽十分钟把 ffmpeg 的基本命令记住。因为 ffmpeg 是整个音视频生态的地基,学会了它,以后遇到什么格式转换、尺寸裁剪、字幕提取,都能手到擒来,不用再到处找软件。
4. 进阶玩法:加密流、字幕与批量处理
4.1 带 AES-128 加密的分片怎么处理
不少视频网站的 HLS 流其实带了加密,最常见的是 AES-128。如果你下载的分片文件本身已经是可以播放的解密后内容,那直接无视这一节;但如果你合并时发现画面上全是雪花点、花屏,或者 ffmpeg 报错提到 key 相关字样,那就要检查一下加密问题了。
先看一下你的 m3u8 文件里有没有这样的标记:
grep EXT-X-KEY index.m3u8如果输出里包含METHOD=AES-128、URI="key.key"、IV=0x...这些字段,说明这个流是加密的,而你手里应该有对应的 key 文件。解决的关键点是把 key 文件放在 m3u8 指定的相对路径下,然后不要让 ffmpeg 去合并单个 TS,而是让它直接读取 m3u8 地址:
ffmpeg -i index.m3u8 -c copy output.mp4没错,ffmpeg 自己会解析 m3u8 里的加密参数,边解密边合并。很多人不知道这个技巧,结果手动把加密的分片用 concat 合并,合出来的文件是花的。我之前也在这个坑里浪费过不少时间,后来养成一个习惯:下载 HLS 视频时一定把 m3u8 和 key 文件一起保留,这是最稳妥的。
4.2 字幕文件单独封装或烧录
视频网站的字幕一般有两种形式:一种是直接压在画面里的硬字幕,这种你不需要额外处理;另一种是独立的字幕文件,常见的是.vtt格式,下载 TS 分片时它往往会一并被下载下来。
如果你想把字幕和视频封装到一个文件里,可以这样:
ffmpeg -i output.mp4 -i subs.vtt -c copy -c:s mov_text output_with_subs.mp4这条命令把字幕文件作为一条字幕流封装进 MP4,播放时大部分播放器都会自动识别并显示。
如果你想把字幕“烧”进画面里,也就是变成硬字幕,用这个:
ffmpeg -i output.mp4 -vf subtitles=subs.vtt -c:a copy output_burned.mp4这里要注意一点:subtitles滤镜在 Linux 上依赖字体渲染库,如果烧录时提示字体缺失,或者中文显示成方块,一般是系统里缺少中文字体。解决办法是安装相应语言包,或者在命令中指定一个中文字体路径。Windows 和 macOS 上一般不会有这个问题。
4.3 一键批量合并整个剧集
如果你下载的是一整季剧集,每集一个文件夹,里面都是 TS 分片,那一个个手动执行命令效率太低。写一个简单的循环脚本,几分钟就能处理完所有剧集。
for dir in */; do cd "$dir" ls *.ts | sort -V | while read f; do echo "file '$PWD/$f'"; done > list.txt ffmpeg -f concat -safe 0 -i list.txt -c copy -bsf:a aac_adtstoasc -movflags +faststart "../${dir%/}.mp4" cd .. done这个脚本的逻辑是:遍历当前目录下的每一个子文件夹,进入后生成分片列表,合并输出到上一级目录,命名为对应文件夹的名字。执行前先确认一下你的一集文件确实都放在单独的文件夹里,顺序没混在一起,否则脚本处理出来的可能有“串台”问题。
我第一次用这个脚本时,忽略了 Windows 和 macOS 上路径分隔符的差异,结果在 Windows 上跑了一堆“路径找不到”的错。后来我学到的经验是:跨平台写脚本尽量用路径拼接方式,或者干脆把子目录重命名成简单的数字编号,能省很多麻烦。
5. 常见问题速查表与排查技巧
5.1 六类高频报错速查表
我把平时被问得最多的问题整理成了一张表,方便你按症状快速定位。
| 问题症状 | 可能原因 | 推荐解法 |
|---|---|---|
| 合并后的视频顺序乱跳 | 文件名按字典序排列,10.ts 排到了 2.ts 前面 | 用sort -V重新生成 list.txt,或参照 m3u8 顺序 |
| 有画面但没有声音 | 缺少aac_adtstoasc过滤器,AAC 封装格式没转换 | 在命令中加上-bsf:a aac_adtstoasc |
| ffmpeg 提示 “Unknown encoder” | 安装的是精简版,缺少 x264 编码器 | 换一个静态编译完整版,或重新安装 ffmpeg |
| 输出文件无法在手机/电视上播放 | 视频编码是 H.265,或 moov 元数据在文件尾部 | 用 libx264 重编码,并加-movflags +faststart |
-c copy合并时直接报错 | 某个分片损坏,或编码参数不一致 | 用 ffprobe 检查分片,删除异常文件,必要时整体重编码 |
| 合并后音画不同步 | 某个分片缺帧,或时间戳不连续 | 删除坏分片,加-fflags +genpts重新生成,或整体重编码 |
这张表没法覆盖所有异常情况,但大多数人的问题都能在里面找到对应解法。如果你遇到的是表格之外的情况,先把 ffmpeg 的报错日志截图或复制下来,很多时候报错信息里就已经写明了建议的处理方式。
5.2 通过日志和 ffprobe 验证产物质量
很多朋友合并完之后,看到文件生成了就以为大功告成,结果播放了几分钟才发现问题。所以我建议养成一个习惯:合并完成后,先用 ffprobe 验证一下输出文件的时长和总码率,再打开播放器从头到尾快速拖一下看看。
判断分片是否完整的标准是:把每个 TS 分片的时长加起来,再和输出 MP4 的时长对比。误差在 1 到 2 秒以内属于正常,如果差了几分钟,那基本可以确定有分片没被合并进来,或者路径没读全。
这里有一个很实用的小命令,可以快速查看输出文件的总时长和编码信息:
ffprobe -v error -show_entries format=duration -show_entries stream=codec_name,codec_type -of default=noprint_wrappers=1 output.mp4如果输出显示的视频编码是 h264,音频编码是 aac,时长也符合预期,那这个文件的问题就不大了。遇到顽固性问题时,我一般会先用播放器直接打开源目录里的单个 TS 文件播放一下,确认是文件本身的问题,还是合并步骤的问题,这样排查起来快得多。
我个人在实际操作中最大的体会是:不要贪快,合并前多花两分钟看 m3u8、检查分片信息,比合并后反复返工要划算得多。尤其那种看起来很有规律的命名顺序,反而容易在某个不起眼的节点上翻车。工具再多再好,都不如自己手里有一套稳定、可复现的方法来得踏实。