简介:untrunc是一套用于恢复损坏(截断)的MP4、M4V、MOV、3GP等视频的C++开源工具,面向有命令行基础的中级开发者和视频后期维护者。它通过参照一个完好的同源视频来修复受损文件,适用于录像中断、导出异常等场景,对M4A等音频同样有效。包内共42个文件,以23个C++源文件(cpp)和10个头文件(h)为主,覆盖编解码器解析、轨道处理、原子解析等核心逻辑,另附工程配置文件及Dockerfile,便于二次编译与容器化使用。压缩包仅72KB,代码精简但功能完整,适合学习MP4容器结构与容错恢复原理。目前已有1670人学习/下载,说明其在实际问题中的参考价值。通过阅读源码,读者可掌握基于Libav的修复思路,并针对具体损坏情况调整恢复策略。
1. untrunc救的是哪类视频:截断、打不开、但数据还在的mp4与mov
很多“恢复视频”的需求不是来自剪辑事故,而是来自“文件还在,结尾没了”。无人机航拍时电量耗尽、行车记录仪被直接断电、手机录到一半内存卡被拔出,都可能让一段mp4、m4v、mov或3gp只留下残缺的文件头。系统播放器打不开,ffprobe也只回一句“moov atom not found”。untrunc就是专治这类截断损坏的开源命令行工具:它不靠瞎猜,而是拿一个类似的、没有中断的视频当模板,把坏文件里残余的帧数据重新拼成可播放的mp4。它适合两类人:手里还有同一台设备录过完好视频的人,以及想批量处理监控、相机备份目录里一堆打不开文件的人。读完这篇,你能搞明白它为什么能救、什么时候救不了、怎么一次跑通。
2. 先看MP4盒子结构:moov被切断为什么就“救不了”
修复截断mp4之前,得先理解一个反直觉的事实:mp4不是一坨连续编码的数据,而是由一堆叫“box”的盒子嵌套组成的。播放器打开文件时,并不是老老实实从头读到尾,而是先找“目录”,再按目录跳到具体位置取数据。所以才会出现“文件有几百MB却打不开”的情况——因为目录丢了,数据再多也没人知道从哪一帧开始播。
2.1 MP4是盒子套盒子:ftyp打头、mdat装数据、moov当目录
一个常规MP4文件的开头是ftyp box,它声明文件使用哪个QuickTime/MP4规范、支持的品牌,体积很小,起“门牌号”作用。紧接着大概率是moov box,里面装着mvhd、trak、mdia、stbl这一长串元数据;再往后是mdat box,里面才是真正的H.264/H.265码流和音频采样数据。其中moov里的stbl子box尤为重要,stco记录每一帧在文件里的绝对偏移,stsz记录每一帧的大小,stts记录每一帧的时刻与时长。播放器靠这一整套索引,把mdat里一个又一个“图像切片”按时序解码出来。
如果moov在文件尾部,而损坏恰好从尾部切掉,moov往往整个消失。播放器打开文件时找不到目录,只能报错。你可能觉得“数据都还在,为什么不能从头扫到尾播呢”——因为mp4不是流媒体直播那种扁平结构,没了stco,播放器无法知道哪些字节是帧数据、哪些字节是box头、哪些是音轨交叉写入的间隔;强行按顺序解,大概率第一帧都找不到。
反过来说,那些做了faststart优化的文件,moov被前置到文件头部,截断时通常只丢mdat尾部的几秒数据,文件依然能打开。这类文件根本不需要untrunc,用ffmpeg直接重新封装一次就能出片。所以真正需要修复的是“moov丢了、mdat还在”的文件。
还有个知识点直接影响修复成功率:视频轨如果是H.264,帧数据内部自带start code(00 00 00 01),untrunc扫描mdat时可以借此找到每一帧的边界。如果视频轨是H.265,结构类似但参数集名称不同,老版本untrunc可能识别不了,需要换支持HEVC的新版或手动指定codec。后面第5章会专门讲这个坑。
2.2 三种高发的截断姿势:录制中断、拷贝中断、索引被腰斩
最常见的场景是录制中断。手机、无人机、行车记录仪在异常断电时,文件系统被强制卸载,实际写入速度跟不上的部分直接丢失。这种损坏通常是“文件末尾缺了一段”,前边几百MB码流完好,只是moov没写进去或写了一半。untrunc对这种文件的恢复成功率最高,因为残余数据质量高,尺寸也完整。
第二种是拷贝中断。从相机SD卡拷到电脑时拔卡、断USB、网络传输到一半失败,导致目标文件的大小停留在某一时刻。这里要注意:某些“拷贝中断”的文件,操作系统文件表里的大小比实际数据还小或正好卡在某个box的中间,修复时依然能找回大部分内容;但如果是U盘主控写入时掉电,可能连MDAT中间都缺了数据块,恢复出来就会花屏或跳帧。
第三种是索引被腰斩。部分录制设备默认把moov放到文件末尾,整个文件长这样:ftyp + 少量free + 全部mdat + moov。你用网盘或FTP传输这种文件时,如果传输在中途被掐断,丢的正是最后的moov。这种场景下数据几乎100%完好,缺的只是“目录”,untrunc恢复效果极佳。
判断坏文件是否值得修复,有一个朴素标准:先看文件大小。一个正常1080p手机录像,每秒码率约10-20Mbps,1分钟大约100多MB;如果坏文件只剩几百KB,说明连数据都被砍光了,这时结构修复没有任何意义。先用ls -l bad.mp4看一眼体积,再决定要不要折腾,这是最省时间的步骤。
2.3 动手前先用十六进制确认里头还有“货”
开工前我一般会做一步快检:用十六进制工具看一眼坏文件头部和尾部,确认ftyp还在、mdat还在,只是moov丢了。熟悉本领域的人称之为“先摸清坏文件的家底”,避免白跑一晚上。
# 查看文件头部:能正常看到 ftyp 占前几字节 xxd bad.mp4 | head -20 # 搜索文件里的关键 box 标识:moov、mdat 出现的位置 grep -aob "ftyp\|moov\|mdat" bad.mp4 | head -20xxd展示前十几行,主要确认文件头不是全零,说明不是空文件。grep -aob会把ftyp、moov、mdat三种box标识在文件里的字节偏移打出来:如果能看到mdat偏移值,但找不到moov,这就是untrunc能处理的典型形态;如果连mdat都找不到,说明数据层损坏,直接放弃。这条命令不会修改原文件,可以放心跑。做完这一步,你对“值不值得修”就有底了。
注意:
grep -aob的偏移量是十进制字节位置。moov一般出现在文件头附近或文件尾部,如果你只看到ftyp和free,没有moov,那么修复希望很大。
3. 准备工具链:编译untrunc并挑一个正确的参考文件
untrunc不是一个能下载完直接双击的图形程序,它本质上是C源码,依赖FFmpeg的库来解析容器和解码参数。在不同的发行版上编译差异不小,但整体思路一致:先把FFmpeg开发库装好,再编译源码,最终得到一个可执行的二进制。本节把Linux和macOS的主流程都讲一遍,Windows用户可以在WSL里跑同样命令。
3.1 编译安装:从源码到可用二进制
系统依赖方面,Debian/Ubuntu系通常需要libavformat-dev、libavcodec-dev、libavutil-dev、libavdevice-dev这几个包;macOS用户用Homebrew安装ffmpeg时,需要带上能输出静态库的选项。装好之后,进入untrunc源码目录编译。
# 进入源码目录,先看仓库里是否自带Makefile ls -l # 自带Makefile时直接编译 make # 如果make报错或链接不通过,可以手工编译: # gcc -o untrunc untrunc.c -lavformat -lavcodec -lavutil -lm # 编译成功后验证可用性 ./untrunc不带参数直接运行./untrunc会打印usage帮助信息,这是确认编译成功最直接的方法。如果提示缺少libavformat.so,说明动态库路径没找到,常见做法是检查ldconfig -p | grep libavformat,或者把FFmpeg安装目录加入LD_LIBRARY_PATH。这里最绕人的坑是老版本untrunc源码与新版本FFmpeg库的API不兼容,编译报错时不要死磕,直接换一个新的fork版本源码再试,这种坑不值得花时间。
3.2 参考文件怎么挑:只有名称相似是不够的
untrunc的工作原理决定了它必须有一个参考文件。坏文件与好文件的编码格式、分辨率、帧率越接近,恢复出来的结果越可靠。最理想的参考文件就是同一台设备、同一个拍摄模式下,紧挨着坏文件录的那一段正常视频。比如无人机连续拍摄时,航段1损坏了,航段2完好,那么航段2就是救航段1最好的模板。
参考文件至少要满足三点。第一,视频编码一致:H.264的就是H.264,H.265的就是H.265,混用基本白搭。第二,分辨率完全一致:同样是4K,3840x2160和3840x2160不能差一像素。第三,音视频轨道结构一致:如果坏文件是双音轨录音设备录的,参考文件也最好有同样多的音轨,不然修复后可能丢音轨。下面是挑选参考文件时的检查清单。
| 检查项 | 命令 | 要求 |
|---|---|---|
| 编码格式 | ffprobe -v quiet -show_streams -select_streams v:0 -show_entries stream=codec_name good.mp4 | 与坏文件一致 |
| 分辨率 | 同上,看width、height | 严格一致 |
| 帧率 | ffprobe -v quiet -show_streams -select_streams v:0 -show_entries stream=avg_frame_rate good.mp4 | 与坏文件预期接近 |
| 音轨数 | ffprobe -v quiet -show_streams -show_entries stream=codec_type good.mp4 | 与坏文件录制时的音轨数一致 |
3.3 实在找不到参考文件时怎么办
如果你手里只有孤零零一个坏文件,可以先别慌。手机厂商、运动相机、行车记录仪这类固定参数设备,你手头随便找一个同分辨率、同时长的旧视频,哪怕不是同一个日期拍的,也值得一试。常见做法是先用ffprobe扫出你手头所有mp4的分辨率和编码,筛出匹配项,再逐一作为参考文件跑untrunc,最后拿ffprobe验证哪个结果能读出时长和SPS信息。这个“用候选列表试一圈”的过程不复杂,但能救回意想不到的文件。
注意:参考文件不要拿转码后的文件。用格式工厂或剪辑软件二次导出过的mp4,容器参数已经重写,和相机直出的结构相差很大,修复成功率会明显下降。优先找原始直出的文件。
4. 跑通恢复流程:最小命令、参数与结果验证
工具链就绪后,真正执行修复反而很简单。核心思路就是一条命令:untrunc后面跟两个文件,第一个是正常参考文件,第二个是损坏文件。本节从最小命令讲起,再展开参数怎么选,最后给出恢复后必须做的三连验证。
4.1 最小恢复命令:先跑出结果再说
# 语法:untrunc 参考好文件 损坏文件 ./untrunc good.MP4 bad.mp4 # 常见变体:指定输出的文件名 ./untrunc -o fixed.mp4 good.MP4 bad.mp4执行过程中untrunc会先读取参考文件的moov元数据,再扫描坏文件里的mdat数据,最后把两者合并成新文件。命令行里第一个参数永远是参考文件,第二个才是损坏文件,这个顺序反了会直接报错或者把好文件覆盖掉。合并没有发生之前,原文件不会被改动,这一点还算安全。扫描和解析通常需要几秒到几十秒,取决于视频时长和硬盘读取速度,耐心等它跑完,输出文件默认生成在当前目录,具体文件名以运行./untrunc时打印的usage为准。
4.2 核心参数:按usage走,别照搬老教程
不同分支的untrunc参数略有差异,最稳妥的方式是先在终端跑一次./untrunc,看它自己打印的帮助,再按需取用。但通用高频参数基本跑不掉:-o指定输出文件路径,批量修复时尤其好用,避免默认文件名把参考文件顶掉;-q进入静默模式,脚本里循环跑的时候能减少大量中间日志;-c手动指定视频编码方式,当自动探测失败、但你能确定坏文件是H.265时,有的版本支持这样强制指定。
还需要理解-d这类时长参数:有些版本支持限制处理时长,只恢复坏文件前N秒的内容。当文件后半段已经完全损坏、但你还想抢救前面几分钟素材时,给untrunc一个合理的时长上限,可以避免它在损坏区反复尝试导致输出文件无法播放。至于具体参数名,不同分支确实不同,我通常的做法是先把usage输出保存下来,再动手写脚本。
4.3 恢复后的三连验证:ffprobe、ffplay、全解码测试
修出来的文件能不能用,不能只说“播放器能打开”。我一般按下面三步验证,每步都有不同的目的:
# 1. 用 ffprobe 确认容器结构:时长、分辨率、帧率是否已恢复 ffprobe -v error -show_entries format=duration,size:stream=codec_name,width,height,avg_frame_rate -of default=noprint_wrappers=1 fixed.mp4 # 2. 用播放器实际播一遍,确认能出画面且有声音 ffplay -autoexit fixed.mp4 # 3. 全解码测试:逐帧过一遍解码器,检查有没有花屏或解码错误 ffmpeg -v error -i fixed.mp4 -f null - 2>&1 | head -50第一步看结构信息,duration字段是不是大于零,是不是和坏文件应有时长接近;第二步是主观检查,重点看开头和结尾两处有没有绿屏、卡帧、声音是否同步;第三步是把整段视频完整解码一遍,-f null -只解码不输出,任何解码错误都会打印到stderr。三步全过,再谈交付。如果第三步刷出一堆error,说明修复文件只能“打开”,不能“正常播”,后期转码也会中途失败。
5. untrunc避坑排查:恢复失败和“能放但不对”的五个高发问题
untrunc不是万能后悔药,它有自己的失效边界。下面这五类情形是真实使用中最高发的坑,每一条都按“现象-原因-解决”展开,看过之后能帮你省下大量试错时间。
5.1 “moov atom not found”,但文件头还在
现象:ffprobe直接报错打不开,但用xxd看文件头ftyp完全正常,大小也有几百MB。原因:moov box位于文件尾部,被截断整段删掉了,播放器和ffprobe都无法重建索引。解决:这正是untrunc的典型对口场景,直接执行最小修复命令即可。如果执行时提示“cannot find video track”之类信息,先确认参考文件的视频track存在,再看坏文件里mdat是否完整。若grep -aob只能搜到mdat的box头,但后续偏移比实际文件大小还大,那就是文件被截断在了mdat中间,这种修复出来通常后半段是绿屏,实际价值有限。
5.2 恢复成功但时长不对,画面快进
现象:修复出来的视频能正常播放,但总时长远小于参考文件,比如参考文件有30分钟,恢复结果只有3分钟,画面像是被快进。原因:坏文件丢失了部分帧数据,或者SPS/PPS引用的帧率信息与参考文件不一致,导致stts时间基错乱。解决:检查参考文件是否真的匹配——分辨率相同、帧率相同、编码profile相同,差一个都不行;另外明确一点,untrunc恢复的是“能找回多少数据”,不是变魔术补全画面。如果3分钟已经是坏文件里全部完好的数据,那就接受这个结果。想进一步确认,用第4章的ffprobe看输出文件avg_frame_rate是否在合理范围。
5.3 没有参考文件时,靠同规格视频“碰运气”
现象:坏文件来自某台已经报废的旧设备,找不到任何同机素材,我拿手机随便录了一段同分辨率视频当参考,执行后报“track size mismatch”或输出花屏。原因:untrunc会把参考文件的moov容器参数直接套用到坏文件上,如果参考文件的GOP结构、编码profile、音频采样率与坏文件差距过大,扫描出的帧数量与容器索引无法匹配。解决:唯一可行路径是扩大候选池,把能拿到的同规格mp4全部试一遍,用ffprobe验证每次输出是否有正常时长。这个做法不保证成功,但运气好的时候能救回某些老设备素材。也有商业工具能做无参考文件恢复,但那个优先级低,先把untrunc这条便宜的路径走完再考虑。
5.4 H.265/HEVC视频修复出来全是绿屏
现象:运动相机的高帧率H.265视频修复成功,但播放时画面全是绿色马赛克,一点有效内容都看不到。原因:老版本untrunc对HEVC的解析支持不完整,VPS/SPS/PPS参数集没有被正确写入输出文件。解决:优先尝试支持H.265的更新分支版本;如果编译环境不便更新,检查是否存在-c h265之类的codec指定参数,强制按HEVC解析。千万不要指望一个旧二进制通吃所有编码,拿不同版本各跑一次再用ffplay验证,成本低收益高。
5.5 修出视频后音频丢失或音画不同步
现象:修复后的mp4视频轨完好,但完全没有声音;或者有声音却比画面晚半秒。原因:参考文件音轨参数与坏文件不一致,或者坏文件的音频数据在截断时被部分丢弃,untrunc扫描时没有找到可配对的音频帧。解决:先用ffprobe看参考文件有没有音轨、声道数是多少;如果参考文件是单声道而坏文件录制时是立体声,尽量找一个音轨参数吻合的参考文件。对于音画不同步,可以用ffmpeg将修复文件与原始音频重新封装,处理步骤见下面命令块。这一步属于事后补救,能解决一部分不同步问题,但不能凭空造出丢失的音频轨道。
# 提取坏文件里还能读出的音频轨(前提:ffmpeg 能部分解析坏文件) ffmpeg -v error -i bad.mp4 -vn -acodec copy audio.m4a # 重新封装:用修复后的视频轨 + 提取出来的音频轨合成新文件 ffmpeg -v error -i fixed.mp4 -i audio.m4a -map 0:v:0 -map 1:a:0 -c copy final.mp46. 把恢复做成脚本:批量修复与自动验证
单文件修复跑通后,真正能派上大用场的场景是批量处理。比如监控摄像头SD卡拔出来后,里面几十个mp4全部损坏;或者相机备份目录里混杂着大量打不开的素材。逐条手敲命令既费时又容易漏,我一般会写一个简单的bash循环,自动完成“找参考文件-执行修复-自动验证”三步,并把验证结果打印成清单。
#!/bin/bash # batch_fix.sh:批量恢复目录里的截断 mp4 GOOD_DIR="./good" # 参考视频目录 BAD_DIR="./bad" # 损坏视频目录 OUT_DIR="./out" # 恢复输出目录 mkdir -p "$OUT_DIR" for bad in "$BAD_DIR"/*.mp4; do [ -f "$bad" ] || continue name="$(basename "$bad")" good="$GOOD_DIR/$name" if [ ! -f "$good" ]; then echo "SKIP $name : no reference file" continue fi out="$OUT_DIR/${name%.mp4}_fixed.mp4" untrunc -q -o "$out" "$good" "$bad" || { echo "FAIL $name" continue } dur=$(ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 "$out" 2>/dev/null) echo "OK $name -> duration=${dur:-0}s" done这个脚本的逻辑不复杂:遍历坏文件目录,找同名参考文件,找不到就跳过;执行untrunc时加上-q静默参数,避免日志淹没关键信息;恢复完成后立即用ffprobe抽取时长,时长为空或为零就说明这次修复没有产出有效文件。这么做最大的好处是能筛掉大量无效结果,只保留下真正可以进后期流程的素材。恢复出来的成品如果头尾有多余黑场,还可以用ffmpeg无损切割掉首尾两秒,把素材修整成可直接剪辑的状态。
我第一次批量处理这类素材时,就是因为没有按设备区分参考文件,把同一台无人机里4K和1080P两种规格混在一起跑,结果恢复出来一半文件时长异常,白跑了一晚上。后来养成了习惯:批量修复前先按分辨率和编码分组,每组单独指定参考文件,跑完再自动验证一遍——这步排查动作救回了我后来好几批监控素材。希望这些流程也能帮到你少踩坑。
本文还有配套的精品资源,点击获取