You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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同时访问同一块内存区域,彻底规避文件系统层面的读写冲突。

步骤:

  1. 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();
    }
    
  2. 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库,适合进阶场景:

  1. C#端通过ZeroMQ发布系统使用率数据;
  2. FFmpeg端使用zmq滤镜订阅数据,传递给drawtext显示。

内容的提问来源于stack exchange,提问作者Vlad Stefan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 13:24:56