C#控制台录屏时FFmpeg更新Overlay文件崩溃求助
问题场景
开发C#控制台录屏程序,通过FFmpeg的drawtext滤镜在录制画面中实时显示CPU、RAM、GPU使用率。运行2-3小时后录制会意外停止,原因是FFmpeg读取Overlay文本文件时,C#程序正尝试更新该文件,导致读写冲突。尝试过临时文件替换、调整reload帧数等方法,均无法彻底避免崩溃。
直接更新文件时FFmpeg报错
Error: [Parsed_drawtext_0 @ 000001e68fd95dc0] [FILE @ 0000009fb7ffee70] Error occurred in CreateFileMapping() Error: [Parsed_drawtext_0 @ 000001e68fd95dc0] The text file 'OverlayFiles/OverlayFile_MyPC.txt' could not be read or is empty Error: [vf#0:0 @ 000001e68fd48300] Error while filtering: Operation not permitted Error: [vf#0:0 @ 000001e68fd48300] Task finished with error code: -1 (Operation not permitted)
临时文件替换时FFmpeg报错
Error: [Parsed_drawtext_0 @ 0000014c815e6200] [FILE @ 000000253d7fee70] Cannot read file 'OverlayFiles/OverlayFile_MyPC.txt': Permission denied Error: [Parsed_drawtext_0 @ 0000014c815e6200] The text file 'OverlayFiles/OverlayFile_MyPC.txt' could not be read or is empty Error: [vf#0:0 @ 0000014c81597280] Error while filtering: Permission denied Error: [vf#0:0 @ 0000014c81597280] Task finished with error code: -13 (Permission denied) Error: [vf#0:0 @ 0000014c81597280] Terminating thread with return code -13 (Permission denied)
当前使用的FFmpeg参数
string arguments = $"-video_size 1920x1080 -framerate 30 -f gdigrab -i desktop -c:v libx264rgb -crf 0 -preset ultrafast -color_range 2 " + $"-vf \"drawtext=fontfile=C\\:/Windows/fonts/consola.ttf:fontsize=30:fontcolor='white':textfile={overlayFilePath_}:boxcolor=0x00000080:box=1:x=10:y=H-210:reload=1\" \"" + outputFile + "\"";
已尝试的C#更新文件代码
1. 直接写入版本
public void UpdateOverlayText(string filePath) { string usage = GetSystemUsage(filePath); // Get the system usage data try { // Open the file with FileShare.ReadWrite to allow other processes to read it while writing using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) { using (var writer = new StreamWriter(fileStream)) { writer.Write(usage); // Write the system usage data to the file } } // Ensure file permissions are set correctly after writing SetFilePermissions(filePath); } catch (IOException ex) { Console.WriteLine($"Error updating overlay file: {ex.Message}"); } }
2. 使用MemoryStream版本
public void UpdateOverlayText(string filePath) { string usage = GetSystemUsage(filePath); try { using (var memoryStream = new MemoryStream()) { using (var writer = new StreamWriter(memoryStream)) { writer.Write(usage); writer.Flush(); memoryStream.Position = 0; File.WriteAllBytes(filePath, memoryStream.ToArray()); } } SetFilePermissions(filePath); } catch (IOException ex) { Console.WriteLine($"Error updating overlay file: {ex.Message}"); } }
3. MemoryStream+临时文件版本
public void UpdateOverlayText(string filePath) { string usage = GetSystemUsage(filePath); string tempFilePath = filePath + ".tmp"; try { // Write to a temporary file first using (var memoryStream = new MemoryStream()) { using (var writer = new StreamWriter(memoryStream)) { writer.Write(usage); writer.Flush(); memoryStream.Position = 0; File.WriteAllBytes(tempFilePath, memoryStream.ToArray()); } } File.Replace(tempFilePath, filePath, null); } catch (IOException ex) { Console.WriteLine($"Error updating overlay file: {ex.Message}"); } finally { if (File.Exists(tempFilePath)) { File.Delete(tempFilePath); } } }
稳定解决方案
方案1:使用内存映射文件(Memory Mapped File)替代普通文本文件
内存映射文件可让C#和FFmpeg同时访问同一块内存区域,彻底规避文件系统层面的读写冲突。
步骤:
C#端创建并维护内存映射文件:
录屏前初始化固定大小的内存映射文件,定期写入系统使用率数据,全程不占用独占锁。private MemoryMappedFile _mmf; private MemoryMappedViewAccessor _accessor; private readonly int _bufferSize = 1024; // 足够容纳系统使用率文本 // 初始化内存映射文件(录屏开始前调用) public void InitMemoryMappedFile(string mapName) { _mmf = MemoryMappedFile.CreateOrOpen(mapName, _bufferSize, MemoryMappedFileAccess.ReadWrite); _accessor = _mmf.CreateViewAccessor(0, _bufferSize, MemoryMappedFileAccess.ReadWrite); } // 更新内存中的使用率数据 public void UpdateSystemUsage() { string usage = GetSystemUsage(); // 获取CPU/GPU/RAM使用率文本 byte[] buffer = Encoding.UTF8.GetBytes(usage.PadRight(_bufferSize, '\0')); // 填充到固定长度,避免读取截断 _accessor.WriteArray(0, buffer, 0, buffer.Length); } // 录屏结束后释放资源 public void Cleanup() { _accessor?.Dispose(); _mmf?.Dispose(); }FFmpeg端读取内存映射文件:
修改drawtext参数,通过Windows内存映射文件协议读取数据,设置reload=1确保实时更新。string mapName = "SystemUsageMap"; string arguments = $"-video_size 1920x1080 -framerate 30 -f gdigrab -i desktop -c:v libx264rgb -crf 0 -preset ultrafast -color_range 2 " + $"-vf \"drawtext=fontfile=C\\:/Windows/fonts/consola.ttf:fontsize=30:fontcolor='white':text='file://./memmap://{mapName}':boxcolor=0x00000080:box=1:x=10:y=H-210:reload=1:eval=frame\" \"" + outputFile + "\"";
方案2:优化文件读写逻辑,降低冲突概率
如果不想改用内存映射文件,可通过以下方式优化文件操作:
改进临时文件替换逻辑
利用原子复制操作确保文件切换无间隙,同时降低FFmpeg的文件读取频率:
public void UpdateOverlayText(string filePath) { string usage = GetSystemUsage(); string tempFilePath = Path.Combine(Path.GetDirectoryName(filePath), $"temp_{Guid.NewGuid()}.txt"); try { // 写入临时文件时允许其他进程读取 using (var fs = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read)) using (var writer = new StreamWriter(fs)) { writer.Write(usage); writer.Flush(); fs.Flush(true); // 强制写入磁盘,避免缓存延迟 } // 原子复制替换原文件,确保切换瞬间完成 File.Copy(tempFilePath, filePath, true); } catch (IOException ex) { Console.WriteLine($"Error updating overlay file: {ex.Message}"); } finally { if (File.Exists(tempFilePath)) { try { File.Delete(tempFilePath); } catch {} } } }
同时修改FFmpeg的reload参数为更大的值(比如reload=30,对应每秒读取一次),减少读取冲突:
string arguments = $"-video_size 1920x1080 -framerate 30 -f gdigrab -i desktop -c:v libx264rgb -crf 0 -preset ultrafast -color_range 2 " + $"-vf \"drawtext=fontfile=C\\:/Windows/fonts/consola.ttf:fontsize=30:fontcolor='white':textfile={overlayFilePath_}:boxcolor=0x00000080:box=1:x=10:y=H-210:reload=30\" \"" + outputFile + "\"";
方案3:直接通过FFmpeg ZMQ滤镜传递数据(无文件依赖)
若FFmpeg启用了ZeroMQ支持,可通过C#发送数据到FFmpeg的zmq滤镜,完全规避文件操作。需要额外引入ZeroMQ库,适合进阶场景:
- C#端通过ZeroMQ发布系统使用率数据;
- FFmpeg端使用
zmq滤镜订阅数据,传递给drawtext显示。
内容的提问来源于stack exchange,提问作者Vlad Stefan
相关产品推荐
相关产品推荐

