ffmpeg录屏开发:暂停分段、时间戳补偿与磁盘保护全攻略 简介这是一套基于Qt与FFmpeg的录屏软件完整工程源码对应“从零开始学习音视频编程技术”系列最终完善版面向已掌握C基础、想将录屏功能做成完整桌面应用的开发者。工程使用Qt 4.8.4实现界面与线程控制FFmpeg 2.5.2负责音视频采集及编码配合MinGW编译环境即可运行熟悉Qt5的读者也能直接打开。压缩包共289个文件以头文件、C源码、lib静态库、dll动态库和ui界面文件为主另含pro工程配置与SDL相关库目录结构便于在Qt Creator中逐步阅读。包体大小17.43MB已有1076人学习下载。资源提供了一套可运行、可扩展的录屏软件最终版覆盖屏幕捕获、音视频编码、SDL渲染与文件封装完整链路结合作者博客中FFmpeg库引用、环境搭建及代码讲解能较快理解时间基换算、线程同步、资源释放等关键实现适合作为音视频入门进阶的参考工程。1. 录屏开发到最终完善ffmpeg 封装里藏着 90% 的翻车点录屏软件做到最后一步往往有一种错觉ffmpeg 命令能出画面了就差不多了。我深度维护过一个某录屏项目真正拉开差距的不是采集和编码而是暂停恢复、分段、磁盘写满、进程被杀这一类边缘情况。这套完善方案的主干仍然是最熟悉的 ffmpeg但最后 90% 的问题其实都出在封装层怎么让分开录制的片段无缝接回一条时间轴怎么在磁盘满之前优雅退出怎么验证最终文件没有花屏。适合谁看适合已经把 ffmpeg 跑通、正在做录屏工具落地和交付的开发者尤其是被暂停逻辑、音画同步和文件损坏折腾过的人。2. 录制管线再梳理ffmpeg 采集、编码与控制三路怎么咬合2.1 为什么用 ffmpeg CLI 做采集编码而不是直接调 libav*直接调 libav* API 写采集、编码、封装理论上更可控但在录屏软件这个场景里成本往往高于收益。桌面录屏要同时处理显示源、音频源、编码器、封装格式、暂停分段、异常中断这些分支全部自己实现排查周期会拉得非常长。用 ffmpeg CLI 最大的好处是管线已经被社区压过无数遍输入设备、滤镜、编码器、muxer 的每一级都能通过参数单独调整出问题时可以用日志定位到底卡在哪一层。以 Linux 桌面为例最基础的录屏命令是ffmpeg -f x11grab -i :0.0 -f pulse -i default \ -c:v libx264 -preset veryfast -crf 18 \ -c:a aac -b:a 128k -ar 44100 -ac 2 \ -movflags faststart \ /tmp/screen.mp4这条命令里有几条关键链路-f x11grab -i :0.0抓取整个桌面-f pulse -i default录系统默认音频输入视频走 libx264 软编码crf 18控制画质preset veryfast控制编码速度音频走 AAC。-movflags faststart会把 moov 元数据移到文件头部避免播放器在完整下载后才能拖动进度条。用 CLI 而不是 API意味着我们只需要管理和维护这一条字符串剩下的采集、编码、封装由 ffmpeg 进程负责。2.2 编码器选型与关键参数软编硬编先做一轮压测录屏对编码器就两个硬性要求实时性要够文件体积要能接受。libx264的preset从ultrafast到veryslow速度越快压缩率越低crf越低画质越好、体积越大。录屏内容大多是窗口切换、文字滚动、鼠标移动这类画面的时间冗余很大crf 18 到 23 之间足够没必要用无损。硬编编码器比如常见的 h264_nvencCPU 占用低但同码率下画质通常不如软编而且在低码率场景会有明显的涂抹感。在最终完善阶段我一般会先做一轮编码器压测再决定默认参数# 测试方案同样录 30 秒分别用软编和硬编然后比较码率与画质 ffmpeg -f x11grab -i :0.0 -t 30 -c:v libx264 -preset veryfast -crf 18 /tmp/soft.mp4 ffmpeg -f x11grab -i :0.0 -t 30 -c:v h264_nvenc -preset p4 -b:v 5M /tmp/hard.mp4测试完用 ffprobe 看实际码率再用播放器在动态画面区域对比。注意硬编必须给一个合理的-b:v不给的话很多硬编编码器会按默认码率输出结果要么模糊要么体积失控。软编的重点是crf硬编的重点是b:v和maxrate。一旦选定默认编码器后续所有分段录制都必须沿用同一套视频参数否则拼接阶段会出现时间戳和画质跳变。2.3 用子进程包住 ffmpeg管道、日志与退出码的正确接法录屏软件的本质是一个壳用户点击开始壳启动 ffmpeg 进程用户点击停止壳让 ffmpeg 正常退出中间所有异常都由壳兜住。常见做法是用 Python 的 subprocess 管理这个进程但这里有三个容易翻车的地方。第一ffmpeg 的日志输出到 stderr不能用stderrPIPE又不读。管道缓冲区一旦被写满ffmpeg 进程会被阻塞录着录着就“静止”了。第二停止时不能直接terminate()或kill()强杀进程会导致 MP4 没有写入 moov文件作废。第三要对退出码做区分ffmpeg 正常处理完退出码是 0被中断或内部错误是非 0。import subprocess, os log_file open(/tmp/ffmpeg_recording.log, ab) proc subprocess.Popen([ ffmpeg, -y, -f, x11grab, -i, :0.0, -f, pulse, -i, default, -c:v, libx264, -preset, veryfast, -crf, 18, -c:a, aac, -b:a, 128k, /tmp/screen.mp4 ], stdinsubprocess.PIPE, stdoutsubprocess.DEVNULL, stderrlog_file) # ... 用户点了停止 proc.stdin.write(bq) proc.stdin.flush() proc.wait(timeout10) print(proc.returncode)这段代码里最关键的是stdinsubprocess.PIPEffmpeg 会监听 stdin 的q命令收到后写完编码缓冲区和音频尾部再退出。stderrlog_file把日志直接落到文件既避免管道阻塞又给事后排查留了依据。proc.wait(timeout10)是兜底防止极少数情况下进程无响应。正常退出时 returncode 为 0录制文件是完整可播的。3. 最终完善一暂停/恢复与音画同步补偿怎么做3.1 原生 ffmpeg 没有暂停分段录制的模型很多第一次做录屏的人会下意识在 ffmpeg CLI 里找“暂停”按钮但 ffmpeg 的交互命令里只有退出和截图没有挂起录制。原因是 CLI 进程惰性地把采集到的帧送进编码器一旦暂停采集音频和视频的时钟就会分叉。与其在进程层面上强行挂起不如把“暂停”重新定义为“结束当前分段”。用户点暂停壳向 ffmpeg 发送q当前分段正常关闭用户点恢复壳启动一个新分段并给新分段设置时间戳偏移让它在时间轴上紧接上一段结束的位置。这个模型不依赖操作系统信号Windows 和 Linux 都能用。代价是每次暂停会产生一个独立文件最后需要拼接。为了避免拼接后时间戳乱跳每个新分段启动时都要显式传入-output_ts_offset参数值是此前已经录制的累计时长。这是整个暂停逻辑里最容易漏的一步。3.2 暂停分段与时间戳补偿-output_ts_offset 参数实战假设第一段录制了 65 秒用户暂停 20 秒后点恢复。如果不设置时间戳偏移第二段的视频 PTS 和音频 PTS 都会从 0 开始拼接播放器会认为第二段与第一段重叠导致画面乱跳或音画错位。正确做法是让第二段的所有 PTS 从 65 秒开始ffmpeg -f x11grab -i :0.0 -f pulse -i default \ -c:v libx264 -preset veryfast -crf 18 \ -c:a aac -b:a 128k \ -output_ts_offset 65.0 \ /tmp/screen_seg_002.mp4-output_ts_offset 65.0是输出选项必须放在输出文件路径之前作用是对所有输出流的时间戳统一加上 65 秒偏移。这样第二段第一帧视频的 PTS 是 65 秒第一帧音频的 PTS 也是 65 秒两路流同步关系保持不变拼接后整条时间轴是连续的。那 65 秒怎么来用 ffprobe 读取上一段 duration 并累加代码里维护一个accumulated_time变量即可。3.3 合并分段concat demuxer 转码的完整脚本分段结束之后需要把多个 MP4 合并成最终文件。常见做法是写一个 concat 列表文件再用 concat demuxer 重新编码一次。为什么要重新编码分段独立录制时虽然编码参数相同但每个文件的时间戳都是从自身起点开始的直接-c copy拼接很容易出现非单调 DTS表现为花屏或播放进度异常。重新编码虽然要花一点时间但稳定。import subprocess, os segments [/tmp/screen_seg_001.mp4, /tmp/screen_seg_002.mp4, /tmp/screen_seg_003.mp4] list_file /tmp/concat.txt with open(list_file, w, encodingutf-8) as f: for seg in segments: f.write(ffile {seg}\n) cmd [ ffmpeg, -y, -f, concat, -safe, 0, -i, list_file, -c:v, libx264, -preset, veryfast, -crf, 18, -c:a, aac, -b:a, 128k, -movflags, faststart, /tmp/screen_final.mp4 ] subprocess.run(cmd, checkTrue)这个脚本里-safe 0允许列表里写绝对路径file ...的行首不能有空格否则解析会失败编码参数要和录制时的参数保持一致避免二次压缩出现画质断层。合并完成后用 ffprobe 确认duration约等于所有分段时长之和。如果暂停期间我们没有录制任何内容那么最终时长应该等于各段时长之和而不是物理时钟经过的时长。3.4 暂停时的关键帧间隔设置暂停分段还会牵扯到一个关键帧问题。MP4 的播放器在定位时依赖关键帧如果分段内关键帧间隔太长拼接后从头播放没问题但用户拖进度条时可能会卡住或倒退。因此录制参数里要显式设置-g和-keyint_min比如-g 60 -keyint_min 60表示每 60 帧一个关键帧在 30 fps 下就是每 2 秒一个关键帧。同时每个分段的视频参数必须完全一致尤其是gop_size和bf设置否则拼接转码时关键帧位置错位会导致二次编码后的画面质量波动。这一条建议直接写进录制模板不要留给用户配置。4. 最终完善二自动分段、磁盘保护与异常退出处理4.1 长时录制自动分段segment muxer 的参数与输出容器坑录屏经常一录就是一两个小时单文件太脆弱只要进程被杀整个文件可能都读不出来。更好用的方案是让 ffmpeg 自己按时间切分用 segment muxer 可以把输出写成xxx_001.mp4、xxx_002.mp4每个分段都是独立可播放的文件。ffmpeg -f x11grab -i :0.0 -f pulse -i default \ -c:v libx264 -preset veryfast -crf 18 \ -c:a aac -b:a 128k \ -f segment -segment_time 600 -reset_timestamps 1 \ -segment_format_options movflagsfrag_keyframeempty_moov \ /tmp/recording_%03d.mp4-segment_time 600表示每 10 分钟切一段-reset_timestamps 1会让每个分段的时间戳从 0 重新开始方便分段独立播放。最关键的坑在-segment_format_options movflagsfrag_keyframeempty_moovMP4 的默认封装会把 moov 延迟写到文件末尾分段结束时如果没正常收尾前半段视频就读不出来。加了frag_keyframe和empty_moov之后moov 分成多个 fragment 跟随关键帧写入即使录制中途断电已经写完的分段也能被大部分播放器识别。如果对兼容性要求更高可以把输出容器改成 MKV-f segment下 MKV 分段的韧性比 MP4 好得多但后续拼接时不那么通用。我的习惯是录屏默认 MP4 加 frag_keyframe交付给别人时几乎不用解释为什么文件能打开。4.2 磁盘空间后卫写满前优雅退出录屏最危险的场景不是蓝屏而是磁盘悄悄写满。ffmpeg 并不知道磁盘剩余空间的概念它只会一直写直到系统返回 ENOSPC然后留下一个无法读取的残缺文件。完善阶段必须加一个空间守卫线程周期性检查磁盘剩余量低于阈值就向 ffmpeg 进程发送q让它正常关闭当前文件。import subprocess, threading, time, shutil MIN_FREE_GB 2 proc None # 假设已经在录制 def disk_guard(): while True: free_gb shutil.disk_usage(/tmp).free / (1024**3) if free_gb MIN_FREE_GB: proc.stdin.write(bq) proc.stdin.flush() break time.sleep(5) threading.Thread(targetdisk_guard, daemonTrue).start()这个守护线程只做一件事每 5 秒检查一次输出目录所在分区的剩余空间低于 2 GB 就触发优雅退出。注意磁盘空间检查要用输出文件的挂载点而不是当前脚本目录很多人在这里写了/但输出写到/tmp会导致守护线程误判。阈值也建议做成可配置项高码率录屏下 2 GB 可能只够撑几十秒要留足用户点击停止的缓冲时间。4.3 异常退出后怎么继续日志定位与文件抢救即使有了空间守卫也防不住突然断电、系统崩溃、显示器休眠这类外部原因。排查问题的第一手资料是 2.3 节里保存的 ffmpeg 日志。日志里常见的错误有Broken pipe、Device or resource busy、Non-monotonic DTS。前两个通常出现在采集源被外部程序占用或显示服务器重置时处理方式是等待几秒后自动重启录制分段最后一个出现在编码或封装层说明时间戳管理出了问题可能就是暂停拼接时没加偏移导致的。如果录制进程确实被强杀但当前输出文件使用了frag_keyframeempty_moov可以尝试用以下命令抢救已经写入的内容ffmpeg -err_detect ignore_err -i /tmp/recording_002.mp4 -c copy /tmp/recovered.mp4-err_detect ignore_err告诉 ffmpeg 忽略解码错误继续读-c copy只重新封装不重新编码。抢救出来的文件尾部可能会有短暂花屏或静音但至少保住了前面录好的内容。如果文件连 moov 都没有抢救成功率会很低这也是我坚持在录制参数里加 frag_keyframe 的原因。5. 录屏避坑与常见问题排查五条一定会遇到的现场记录5.1 停止录制后文件损坏播放器提示无法解析现象用 subprocess 发了q进程退出码也是 0但生成的 MP4 播放到一半卡死或直接打不开。原因输出文件虽然正常关闭了但录制时分辨率或帧率不均匀导致 MP4 的 stts 表异常。解决录制命令里加-vsync cfr强制输出恒定帧率同时把-fps_mode cfr也带上。录屏场景下的显示器刷新率不是稳定 60Hz窗口拖动、动画渲染会造成帧间隔波动CFR 模式能有效减少这类文件损坏。5.2 音画不同步越到后面越明显现象录制一分钟内音画是对得上的录到十分钟后声音明显滞后或超前。原因视频采集时钟和音频采集时钟不是同一个时钟源长期录制后漂移量累积。解决在音频编码前加aresampleasync1让 ffmpeg 在音频流里自动丢采样或补采样与视频时间轴对齐。命令片段-c:a aac -b:a 128k -af aresampleasync1:first_pts0first_pts0表示让音频的起始 PTS 对齐到 0async1表示允许微调采样率偏差。注意这个参数不是一劳永逸的如果录屏中系统音频设备被临时切换仍然可能出现新的偏移此时需要重新启动分段录制。5.3 暂停恢复后第二段画面动作比音频快现象用户点击暂停后过了一分钟再恢复合并后的视频里暂停后的动作“抢拍”音频却还在讲暂停前的内容。原因第二段启动时没有给流加上正确的-output_ts_offsetPTS 从 0 开始拼接时播放器认为两段时间重叠。解决每次启动新分段前用 ffprobe 读取已生成分段的实际 duration 并累加然后把累计值传给-output_ts_offset。最严谨的做法是每次暂停恢复后立即检查合并文件的音画同步而不是等全部录完再检查。5.4 录出来的画面黑屏或鼠标光标消失现象桌面录屏命令能执行进程也在走但视频里是黑屏或者鼠标不见了。原因抓屏源选错了对象比如抓了一个被最小化的窗口或者没有启用光标采集。解决先确认抓的是根窗口而不是某个子窗口Linux 下用xdpyinfo查看当前显示分辨率Windows 下抓全屏用-f gdigrab -i desktop。鼠标光标在 X11 下要加-draw_mouse 1gdigrab 也对应有-draw_mouse参数。还有一类黑屏是显示器休眠导致的这种情况需要在采集源前面加一个超时监测检测到连续黑帧就自动重启分段。5.5 磁盘空间充足但录制中途进程异常退出现象硬盘还剩几十 GB录了十几分钟后 ffmpeg 进程突然消失日志末尾没有明显的编码错误。原因多半是采集设备被系统挂起比如合上笔记本盖子、显示器进入 DPMS 睡眠或者音频设备被其他程序独占。解决不要只盯 ffmpeg要把采集源的生命周期也纳入监控。在录制循环里周期性检测采集设备是否还在最常用的手段是检查 ffmpeg 日志最后写入时间如果超过 5 秒没有新日志且进程仍活着立即发q并重启分段。另外给启动命令加-xerror遇到 X11 错误时让 ffmpeg 立刻返回错误码而不是无响应挂起。6. 用 ffprobe 验证录屏质量交付前必做的三项自检6.1 用 ffprobe 检查流信息与时长录屏交付前我习惯先写一个自检脚本把最终文件的基本信息拉出来。ffprobe -show_streams能看到视频流的编码器、分辨率、帧率音频流的采样率、声道数。以下命令可以直接看关键字段ffprobe -v error -show_entries formatduration:streamcodec_name,width,height,avg_frame_rate,sample_rate,channels -of defaultnoprint_wrappers1 /tmp/screen_final.mp4这条命令不打印雪花日志只输出结构化字段。我会检查duration是否和分段时长之和接近avg_frame_rate是否等于目标帧率音频sample_rate是否匹配。任何一项偏差超过 1% 都要回头查录制参数而不是直接发给用户。6.2 黑帧检测与冻结检测基础信息没问题还要检测画面质量。我常用的两个滤镜是blackdetect和freezedetect。blackdetect找出连续黑屏的片段freezedetect找出画面长时间未变化的片段ffmpeg -i /tmp/screen_final.mp4 -vf blackdetectd1.5:pic_th0.98,freezedetectn0.003:d2 -an -f null -这个命令不写输出文件只把检测结果打到 stderr。d1.5表示黑屏持续 1.5 秒以上才报警pic_th0.98表示画面平均亮度阈值freezedetect的n0.003是噪声容忍度d2是冻结 2 秒以上报警。录屏中黑屏或画面冻结往往是采集源掉线的前兆如果检测到结果我会重新检查采集源和磁盘守护线程。6.3 一键自检脚本最终交付前我会把上述检查写成一个脚本任何机器上跑一遍就能知道这个录屏文件能不能发布。从那以后我每次录完屏都强制走一遍 ffprobe 加黑帧检测再也不靠肉眼拖进度条猜质量。录屏开发的最后一步其实就是把“看起来能用”变成“可验证能用”。希望帮到你。本文还有配套的精品资源点击获取