
1. 先说清楚什么时候会需要合并.ts文件以及为什么要选FFmpeg先说个真实场景你从某个视频平台缓存了一段课程或者用下载工具抓了一个长视频结果到手的不是一个大文件而是一堆part1.ts、part2.ts、part3.ts……几十个甚至几百个每个只有几MB到十几MB。单独双击任何一个都能播但只能播那十来秒拼接后才是完整的片子。想在剪辑软件里做后期或者把视频上传给别人根本没法直接处理这一堆碎片。1.1 ts文件到底是什么格式.ts是 MPEG-TSTransport Stream传输流的缩写全称是MPEG-2 Transport Stream。它最早是数字电视广播用的容器格式特点就是把音视频数据切成固定大小的小包加上时间戳之后一包一包往外传。HLSHTTP Live Streaming这类流媒体协议也沿用了这套逻辑把长视频切成若干小分段客户端边下边播。所以你在看在线视频时其实看到的每一段就是一个小小的.ts文件。这种格式不是给本地保存一整个视频设计的而是给网络传输、实时播放设计的。它耐丢包、可以边下载边播但作为本地文件管理起来非常别扭。而且很多播放器对.ts的支持并不好剪映、Pr 这类软件默认也不认这个扩展名拖着进度条还会卡顿。1.2 手动合并的三类常见场景我见过的需要手动合并.ts的情况基本逃不出下面三类视频下载工具/浏览器插件抓取了 HLS 流。这类工具会把 m3u8 描述文件里引用的所有分片全部下载下来你就拿到了一堆 ts。录制设备默认分段存储。部分直播推流软件、录屏工具、网络摄像头、行车记录仪在长时间录制时为了防止单个文件过大或出错会按配置切成多个 ts 片段。从监控/相机SD卡里拷贝出的历史录像。这类设备导出的视频经常是Chunk01.ts、Chunk02.ts这种命名时间上是连续的但就是分成几十段。碰到这些情况合并成一个完整的.mp4是最合理的处理方式。mp4 通用性最好手机、电视、剪辑软件、网盘预览全都认。1.3 为什么FFmpeg是首选能合并视频的工具很多但 FFmpeg 在合并 ts 文件这件事上几乎是不可替代的原因有三个第一免费且跨平台。Windows、macOS、Linux 都有对应版本不用注册、不用破解、没有水印。市面上的万能视频合并器要么收费要么合出来的视频带广告要么处理几十个文件就开始卡。第二可以直接流拷贝stream copy无损合并。ts 文件的本质是流式封装如果所有分段都来自同一个视频流也就是同一个视频源切出来的FFmpeg 可以用-c copy参数把数据包直接搬运进新容器不用重新编码。这意味着合并速度极快几乎就是复制文件的速度而且画质完全不损失。第三适合批量处理。图形界面工具更适合一次合并三五段视频而 ts 切片动辄几十上百个用终端命令配合列表文件可以一次性解决还可以把命令存成脚本反复用。顺便说一句有人用格式工厂这类软件合并 ts结果等半天才完成——那是因为它在背后做了完整转码画质还可能被二次压缩。FFmpeg 的流拷贝模式非常快一个 2 小时、几 GB 的视频合并往往几十秒就结束了。2. 环境准备Windows和Mac下装好FFmpeg并让它进环境变量在复制本文所有命令之前电脑里得先有ffmpeg这个程序。这步对老手来说是喝水一样的事但对新手来说最容易栽在命令敲下去提示‘不是内部或外部命令’这一步。所以我把安装流程单独拿出来讲。2.1 Windows用winget或手动解压添加PATHWindows 上安装 FFmpeg 有两条主路选一条就行。第一条路用 winget 命令适合 Windows 10/11 较新版本。打开 CMD 或 PowerShell直接执行winget install Gyan.FFmpeg装完新开一个终端窗口运行ffmpeg -version验证。如果提示找不到命令多半是 PATH 环境变量没有刷新重新打开终端就行不需要重启系统。第二条路官网下载 zip 手动解压适合想在任意机器上快速部署的情况。去 FFmpeg 官网的下载页找到 Windows 对应的构建版本下载ffmpeg-release-essentials.zip之类安装包。解压后你会看到一个bin目录里面有ffmpeg.exe、ffprobe.exe、ffplay.exe三个可执行文件。把整个解压后的文件夹放到一个你熟悉的位置比如C:\ffmpeg。然后把这个路径加进系统 PATH右键此电脑→ 属性 → 高级系统设置。点击环境变量在系统变量里找到Path双击编辑。点新建填入C:\ffmpeg\bin确定保存。新开一个 CMD 窗口输入ffmpeg -version。注意如果你把文件夹放在了别的路径PATH 里就必须填对应的路径。这一步的核心是让 Windows 知道ffmpeg这个命令去哪里找。2.2 MacHomebrew和静态版两条路macOS 上最常规的安装方式是用 Homebrew 包管理器。已经装过 Homebrew 的话直接在终端执行brew install ffmpeg它会自动装好 FFmpeg 以及它依赖的一堆库包括 x264、x265、fdk-aac 这些常用编码器。Homebrew 安装通常需要几分钟网速或源有问题的时候可能卡住这都很正常如果等了很久没动静可以换个源或者干脆用下一种方式。如果你不想装 Homebrew或者 Homebrew 安装一直报错直接从 FFmpeg 官网下载 macOS 静态构建版也可以。下载来的包通常是.7z或.tar.xz压缩包解压后里面还是bin目录把三个可执行文件复制到/usr/local/binIntel 芯片或/opt/homebrew/binApple Silicon就能用sudo cp ffmpeg ffprobe ffplay /usr/local/bin/如果 macOS 提示无法打开因为无法验证开发者在系统设置 → 隐私与安全性底部点仍要打开或者对文件执行xattr -d com.apple.quarantine /usr/local/bin/ffmpeg这个命令能去掉系统对下载文件的隔离标记属于老操作了。2.3 验证安装并认识三条核心命令无论哪个系统安装完成后在终端输入ffmpeg -version能看到以ffmpeg version开头的一大段信息就说明安装成功。FFmpeg 自带的另外两个核心程序也顺便认识一下ffmpeg负责转码、封装、合并等一切处理。ffprobe负责读取视频文件信息比如编码格式、分辨率、码率、时长。ffplay一个极简播放器用来快速预览文件。后面章节里你会反复用到ffmpeg和ffprobe这两个命令。新手常有的一个误区是FFmpeg 就是那个转视频的命令其实它是一整套命令行工具包ffprobe在排查视频问题时同样是神器。Windows 用户在 PowerShell 里输入上述命令也可以但要注意PowerShell 里某些符号比如管道|有特殊含义和 CMD 的语法并不完全一样。为了避免麻烦本文中 Windows 的命令我基本按 CMD 来写如果你用的是 PowerShell遇到for循环之类命令直接复制可能用不了建议切换到 CMD或者按代码块里标注的语言来操作。3. 动手前先判断能不能直接无损合并决定了用哪种方法很多新手上来就复制合并命令结果报错回头问我咋回事。其实问题的根源不在命令而在没判断文件之间能不能直接拼。ts 文件虽然设计上支持分段传输但能拼是有前提的。3.1 两个关键前提编码参数一致、时间戳连续第一个前提是所有分段的编码参数必须一致。假设part1.ts的视频流是 H.264、1920x1080、30帧音频流是 AAC、44100Hz那么part2.ts大概率也应该是这样。但如果part2.ts是 1280x720或者帧率变成了 25ffmpeg 流拷贝拼接时会因为流参数不一致而报错或产生怪异结果——比如画面比例突然变化、后半段没声音、播放进度错乱。第二个前提是时间戳连续。ts 封装里的每个数据包都带有 PTS显示时间戳和 DTS解码时间戳如果这些时间戳不按顺序递增合并后的文件在播放器里就会表现为声音画面不同步、拖动进度条卡死、后半段黑屏。一般情况下同一个 HLS 流下载下的分段时间戳是连续的但如果你是用不同来源、不同工具抓下来的 ts拼接时就很容易出问题。3.2 用ffprobe快速检查编码信息判断文件能不能直接拼最简单的方法是抽查其中一两个分片用ffprobe看它的流信息ffprobe -v error -show_entries streamcodec_name,codec_type,profile,width,height,channels,sample_rate -show_entries formatduration -of defaultnoprint_wrappers1 part1.ts输出看起来像这样codec_nameh264 codec_typevideo profileHigh width1920 height1080 codec_nameaac codec_typeaudio channels2 sample_rate44100 duration10.023000然后对part2.ts、part3.ts也跑一遍同样的命令重点对比三行codec_name、width/height、sample_rate。只要这些一致大概率可以直接无损合并。如果不想记那么长的命令用简版也行ffprobe -v error -show_entries streamcodec_name,width,height,channels -of defaultnoprint_wrappers1 part1.ts这条命令会列出每个流的编码和关键参数够用了。3.3 理解两类合并方式流拷贝与重新编码搞清楚了文件状态接下来要理解合并的两种底层方式。这决定了你应该选哪个方法。流拷贝stream copy对应-c copy参数。它不解码、不重新编码只是把 ts 里的视频包、音频包原封不动地搬到新的 mp4 容器里。所以速度快、无损、不占 CPU。前提就是上面说的编码参数一致、时间戳连续、容器兼容。重新编码transcoding对应-c:v libx264这种参数。它先把视频解码成原始像素再用新编码器重新压缩一遍。这样能解决编码参数不一致、时间戳混乱、某些播放器不兼容的问题但代价是耗时、可能损失画质。绝大多数 ts 合并场景都应该优先尝试流拷贝。只有确定不行再考虑重新编码。下面要讲的前三种方法都是流拷贝第四种是重新编码第五种是流拷贝的高级用法。你可以跟着顺序从最简单的开始试。4. 方法一和方法二concat协议与concat列表文件覆盖大部分日常场景4.1 方法一concat协议一条命令合并少量文件FFmpeg 支持一个特殊的输入协议叫concat可以在-i参数里直接通过竖线|拼接多个文件路径。比如当前目录有part1.ts、part2.ts、part3.ts合并命令是ffmpeg -i concat:part1.ts|part2.ts|part3.ts -c copy -bsf:a aac_adtstoasc output.mp4其中-c copy表示流拷贝-bsf:a aac_adtstoasc是音频位流过滤器这里先不展开下一小节会专门讲。运行完会得到一个output.mp4这就是合并好的完整视频。这个方法的优点是命令短、思路直接、不用创建任何中间文件。缺点也很明显文件一多命令就变得又臭又长。如果你有 100 个 ts人是没法手打这么长的命令的。而且 Windows 的命令行有长度上限CMD 约 8191 字符路径太长会直接执行失败。所以我的建议是只有几个到十几个 ts 文件时用方法一。比如你只需要临时拼两三段录像这条命令比建列表文件方便得多。4.2 方法二concat demuxer把文件列表写进txt当文件数量多起来更规范的做法是用 concat demuxer。这个方式的核心是先把所有要合并的文件路径写进一个文本文件然后让 FFmpeg 按照这个文本去读取。首先创建一个list.txt文件名随便起内容格式固定为file part1.ts file part2.ts file part3.ts然后执行ffmpeg -f concat -safe 0 -i list.txt -c copy -bsf:a aac_adtstoasc output.mp4-f concat告诉 FFmpeg 输入格式是 concat 拼接列表-safe 0允许读取列表里的绝对路径和相对路径默认安全模式会拒绝某些路径写法。相比方法一方法二的优势非常明显文件再多也只是一行一行的文本命令长度不受限制。列表文件可以重复使用下次改一改文件名就能继续跑。添加或删减文件很方便不需要改动命令本体。可以结合脚本自动生成列表这就是后面方法三要做的事。方法二是日常使用最频繁的一种我自己的习惯是只要 ts 文件超过 5 个直接写列表文件不要硬凑 concat 协议。4.3 为什么输出MP4要加aac_adtstoasc这条经验很关键也是新手最容易忽略的。很多 ts 文件里的音频流是 ADTS 格式的 AACts 容器里允许这种裸 AAC 流存在但 mp4 容器不允许。mp4 要求音频流带有AudioSpecificConfigASC描述信息否则封装出来的 mp4 可能没有声音或者在某些播放器里直接报错。-bsf:a aac_adtstoasc这个位流过滤器的作用就是在流拷贝时把 ADTS 的 AAC 流转换为带 ASC 的 AAC 流让它可以被正确封装进 mp4。所以只要你的源文件里有 AAC 音频并且输出目标格式是 mp4都建议加上这个参数。虽然某些情况下不加也能成功但加了更保险。如果输出目标是 MKV则通常不需要这个过滤器。4.4 Windows和Mac在文件列表写法上的差异方法二里手动创建list.txt不同系统的处理方式稍有不同。Windows 下用记事本就能创建但要注意编码问题。推荐用 VS Code 或 Notepad 新建文件保存时选 UTF-8 编码。如果你用系统自带的记事本在文件名全是英文数字时没问题一旦文件名里有中文可能会因为默认 ANSI 编码导致 FFmpeg 读取乱码。macOS 自带文本编辑器可以新建纯文本同样注意格式 → 制作纯文本编码用 UTF-8。这里给一个小建议ts 文件名尽量保持英文和数字比如part_001.ts能避开很多跨平台编码坑。Windows 下 CMD 里也可以直接生成列表文件这个放到方法三里讲。手动创建时注意每一行的路径必须用单引号包住即使路径里没有空格也要写file C:\videos\part1.ts如果路径本身含有单引号这种情况极少需要转义处理。实际项目中把list.txt和 ts 文件放在同一个目录用相对路径最省心。5. 方法三和方法四终端自动生成列表、以及编码不一致时怎么办方法二解决了批量问题但手动建list.txt依然麻烦。于是有了方法三直接用终端命令把当前目录下所有 ts 文件名批量写进列表文件。方法四则是前面再三提到的重新编码合并用来兜底编码不一致的场景。5.1 方法三在终端里批量生成列表文件macOS / Linux 用户在 ts 文件所在目录打开终端执行for f in *.ts; do echo file $f list.txt; done原理很简单*.ts由 shell 展开成当前目录下所有 ts 文件名循环里逐行写入file xxx.ts。执行完用cat list.txt检查一下内容再跑合并命令ffmpeg -f concat -safe 0 -i list.txt -c copy -bsf:a aac_adtstoasc output.mp4Windows CMD 用户可以在 ts 文件所在目录执行(for %f in (*.ts) do echo file %f) list.txt注意这条命令里%f是 CMD 命令行里的写法。如果你把命令写进.bat批处理脚本文件就必须把%f全部改成%%f否则脚本会报错。这是 Windows 新手最容易踩的一个差别。Windows PowerShell 用户可以用下面的命令代替Get-ChildItem -Name *.ts | ForEach-Object { file $_ } | Set-Content -Encoding UTF8 list.txt用 PowerShell 的好处是编码明确为 UTF-8坏处是语法和 CMD 完全不同。我的建议是新手统一用 CMD 跑命令因为网上大部分 Windows FFmpeg 教程都是基于 CMD 的。5.2 方法四filter_complex重新编码解决编码不一致和音画不同步假设你已经按方法二生成了list.txt但执行-c copy时 FFmpeg 报了类似下面的错误Codec h264 is not supported by the bitstream filter aac_adtstoasc或者合并出来的视频出现音画不同步、播放卡死、画面花屏说明你的 ts 之间编码参数不统一或时间戳有问题。这时候需要换用重新编码方案。以下几个文件举例part1.ts、part2.ts、part3.ts编码不完全一致可以直接用 filter_complex 把它们合起来ffmpeg -i part1.ts -i part2.ts -i part3.ts -filter_complex [0:v][0:a][1:v][1:a][2:v][2:a]concatn3:v1:a1[v][a] -map [v] -map [a] -c:v libx264 -c:a aac output.mp4这个命令做了解码、统一拼接、重新编码三步[0:v][0:a]表示第一个输入文件的视频流和音频流后面的[1:v]、[2:v]依此类推。concatn3:v1:a1是 concat 滤镜的核心参数n3表示有 3 个输入v1表示输出一路视频a1表示输出一路音频。[v][a]是滤镜输出的两个流标签-map把它们映射到输出文件。-c:v libx264 -c:a aac表示视频用 H.264 重新编码音频用 AAC 重新编码。如果某些 ts 片段没有音频滤镜应改成类似[0:v][1:v][2:v]concatn3:v1:a0[v]的写法同时删除-map [a]和-c:a aac。这种有的有声音、有的没声音的情况比较特殊实际处理时尽量先把源文件统一成都有音频的再做合并。重新编码最大的缺点是慢。一个几十 GB 的视频用-c copy可能一分钟就合完了用 libx264 重新编码可能要几十分钟甚至几小时。所以方法四应该作为兜底方案不要一上来就用。5.3 一个折中方案全部先转成统一ts再合并如果你不想完全重新编码但直接 copy 又报错可以试试先统一封装成 ts再列表合并的折中做法。比如part1.mp4和part2.ts要合并MIME 类型不一样直接 copy 到 mp4 容易出问题。这时可以先统一转成 TSffmpeg -i part1.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts temp1.ts ffmpeg -i part2.ts -c copy -bsf:v h264_mp4toannexb -f mpegts temp2.ts这样把视频流转换成 TS 需要的 Annex-B 格式音频流保持 AAC 即可。然后ffmpeg -f concat -safe 0 -i list.txt -c copy -bsf:a aac_adtstoasc output.mp4这里的h264_mp4toannexb是视频位流过滤器作用与音频那个aac_adtstoasc类似把 MP4 中存储的 H.264 数据转换成 TS 需要的 Annex-B 字节流。这个方法比重新编码快很多适合文件跨封装格式、但内部编码参数其实一致的场景。6. 方法五与高频雷区自然排序合并以及我踩过的坑6.1 方法五自然排序解决1.ts、2.ts、10.ts的顺序错乱前面几个方法默认文件名顺序就是对的但这其实是一个非常容易踩的暗坑。假设你有1.ts、2.ts、3.ts、10.ts、11.ts这些文件用普通的字典序排列结果会是1.ts、10.ts、11.ts、2.ts、3.ts——也就是说第 10 集会被排到第 2 集前面。macOS / Linux可以用ls -1v做自然排序-v参数就是按版本号自然排序的意思。完整流程ls -1v *.ts | sed s/^/file /; s/$// list.txt ffmpeg -f concat -safe 0 -i list.txt -c copy -bsf:a aac_adtstoasc output.mp4这里有两点提醒ls -1v在 macOS 上可用但某些 Linux 发行版的ls -v实现不一定和你预期一致。如果不支持可以用ls -1 *.ts | sort -V | sed ...代替。如果 ts 文件不在当前目录而是分散在子目录里可以用find . -name *.ts | sort -V | sed ...生成路径列表。Windows CMD的自然排序就很让人头疼了。dir /b /on是按字典序排列同样会把 10 排到 2 前面。CMD 里没有内置的自然排序命令我自己通常用三种办法第一种是批量重命名。把所有 ts 文件重命名为固定位数的数字编号比如001.ts、002.ts、003.ts……配合dir /b /on排序就正确了。Windows 自带的文件资源管理器本身能按数字排序你可以在资源管理器里按名称排序看看它已经能把1.ts、2.ts、10.ts正确排好但这只是显示层面的排序命令行的dir并不买账。第二种是用PowerShell脚本生成自然排序列表Get-ChildItem -Filter *.ts | Sort-Object { [int][regex]::Match($_.BaseName, \d$).Value } | ForEach-Object { file $($_.Name) } | Set-Content -Encoding UTF8 list.txt这段代码的思路是把每个文件名末尾的数字提取出来转成整数后按它排序。正则\d$匹配的是文件名最后一段连续数字比如part12.ts会匹配到12。如果你的文件名里没有数字或者数字不在末尾这个正则可能会出错。第三种也是最稳的就是直接用第三方小工具批量重命名。这类工具可以把1.ts、2.ts、10.ts批量改成001.ts、002.ts、010.ts。我自己在 Windows 上处理大量 ts 时一定会先把文件名规范化再跑 FFmpeg 合并流程这样整个流程最不容易出错。6.2 高频报错对照表和排查思路实战中你一定遇到过报错。下面这张表是我工作中最常见的一批错误按出现频率排的报错信息问题原因处理办法Invalid data found when processing input文件损坏、路径错误、文件根本不是有效的 ts检查文件名和路径用 ffprobe 确认文件能否被读取Non-monotonous DTS in output stream时间戳不连续常见于不同来源的 ts先试-fflags genpts不行就重新编码Could not find tag for codec aac in stream #1音频是 ADTS 的 AACmp4 容器不认加上-bsf:a aac_adtstoascCodec ... is not supported by the bitstream filter编码格式不匹配或过滤器选错了检查编码换用对应过滤器或改用重新编码Operation not permittedmacOS 权限限制在系统设置允许终端访问文件或试试xattr -d com.apple.quarantineFailed to open list.txt工作目录不对或 list.txt 不存在确认当前终端所在目录建议用绝对路径指向 list.txt遇到报错时我的排查顺序是先用ffprobe检查文件本身是否可读再检查命令里的路径和文件名是否完全一致再确认编码参数是否统一。这三步能解决八成以上问题。关于Non-monotonous DTS还有一个很实用的思路如果只是时间戳轻微跳变可以在命令里加-fflags genpts让 FFmpeg 重新生成 PTSffmpeg -f concat -safe 0 -i list.txt -c copy -fflags genpts -bsf:a aac_adtstoasc output.mkv注意这里我把输出格式换成了 mkv。mkv 对时间戳异常和编码异常更宽容遇到 mp4 封装卡住时可以先用 mkv 输出确认没问题再转成 mp4ffmpeg -i output.mkv -c copy output.mp46.3 我实际工作中留下的几条习惯合并 ts 这类操作我做过非常多次踩过的坑不少总结几条个人习惯供你参考合并前一定先抽查编码信息。不要嫌麻烦花十秒钟跑一条ffprobe命令比对两个分片的编码参数能避免后面反复报错。如果你发现同一个目录下的 ts 文件并非来自同一个视频源那就要考虑先统一参数再合并。合并完成后不要马上删源文件。输出文件先播放检查一下开头、中间、结尾各拖到几个位置看看确认没有黑屏、爆音、音画不同步再把原始 ts 删掉。否则一旦源文件没了又发现输出有问题那就只能重新下载了。用列表文件时统一在当前目录操作。如果你把list.txt写在别的目录命令里的路径很容易出错。最简单的方法是cd到 ts 文件所在目录然后命令里的列表和输出都直接用相对路径。Windows 下拉出目录的终端可以用地址栏输入 cmd 回车这个技巧比手动cd快得多。命令尽量存成脚本。比如在 macOS/Linux 下把生成列表和执行合并的两步写成一个merge_ts.sh脚本下次遇到同类文件直接双击或bash merge_ts.sh。Windows 下可以写一个.bat文件。虽然第一次写脚本要花几分钟但以后能节省大量重复劳动。善用ffplay快速预览。合并完的临时文件不用急着导入剪辑软件用ffplay output.mp4直接在终端里播放验证比打开大软件快得多。最后还想分享一个方法是我处理超大数量 ts 时的兜底思路如果几个 GB 上百个文件合并过程中途崩了千万别用-c copy反复试同一个输出文件名。把出问题的那个分片单独拿出来用-c copy合并到临时文件检查或者直接把输出封装改成 mkv 再转。宁可多两条命令也不要在一棵树上吊死。