C#中使用ffmpeg提取帧时帧率慢且资源占用高

2024-02-27

我目前正在开发一个项目,需要在 C# 中使用 ffmpeg 从视频中提取帧。但是,我面临帧速率慢和资源使用率高的问题。我使用的代码如下:

private bool move = false;
private int master_frame = 0;

private void pic()
{
    using (Process process = new Process())
    {
        process.StartInfo.FileName = "C:/Users/lenovo/Desktop/ffmpeg.exe";
        process.StartInfo.Arguments = $"-i \"C:/Users/lenovo/Desktop/New folder/video.mp4\" -vf \"select=gte(n\\,{master_frame})\" -vframes 1 -q:v 2 -f image2pipe -c:v bmp -";
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.CreateNoWindow = true;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.RedirectStandardError = true;
        process.Start();

        using (MemoryStream outputStream = new MemoryStream())
        {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = process.StandardOutput.BaseStream.Read(buffer, 0, buffer.Length)) > 0)
            {
                outputStream.Write(buffer, 0, bytesRead);
            }

            pictureBox1.Invoke((MethodInvoker)(() =>
            {
                pictureBox1.Image?.Dispose();
                pictureBox1.Image = new Bitmap(outputStream);
            }));
        }
    }
}

private async void panel1_MouseUp(object sender, MouseEventArgs e)
{
    move = true;
    await Task.Run(() =>
    {
        while (move)
        {
            pic();
            master_frame++;
        }
    });
}

问题是帧速率相当慢,并且资源使用率高于预期。我怀疑读取 ffmpeg 的输出流并为每个帧从 MemoryStream 创建位图可能会导致性能问题。

我将不胜感激任何关于如何优化帧提取过程以获得更好的性能和更低的资源使用的见解或建议。有没有更有效的方法在 C# 中使用 ffmpeg 从视频中提取帧?是否有任何替代方法或优化可以帮助提高帧提取速度?

预先感谢您的帮助和建议!


TLDR:帧速率非常低是由于启动之间存在巨大的延迟ffmpeg进程和实际数据开始发送ffmpeg.

我对您的另一篇文章(“How to Play a Video in a PictureBox using FFmpeg in C#?”)的回答与这篇文章基本相同,展示了如何更快地完成它,尽管仍然不是每秒 60 帧 https://stackoverflow.com/a/76608543/9399492

测量性能

Using Stopwatches,我测量了代码每个步骤所需的时间。

注意:对于较小的单个帧尺寸,我已将格式从 bmp 更改为 png

Code

这是我的测量代码:

private bool move = false;
private int master_frame = 0;

Stopwatch sw = new Stopwatch();
private void pic()
{
    using (Process process = new Process())
    {
        process.StartInfo.FileName = "ffmpeg.exe";
        process.StartInfo.Arguments = $"-i \"video.mp4\" -vf \"select=gte(n\\,{master_frame})\" -vframes 1 -q:v 2 -f image2pipe -c:v png -";
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.CreateNoWindow = true;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.RedirectStandardError = true;
        process.Start();

        using (MemoryStream outputStream = new MemoryStream())
        {
            Console.WriteLine("-");

            sw.Restart();
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = process.StandardOutput.BaseStream.Read(buffer, 0, buffer.Length)) > 0)
            {
                outputStream.Write(buffer, 0, bytesRead);
            }
            sw.Stop();
            Console.WriteLine("Reading bytes: " + sw.ElapsedMilliseconds + "ms");

            framePictureBox.Invoke((MethodInvoker)(() =>
            {
                sw.Restart();
                framePictureBox.Image?.Dispose();
                sw.Stop();
                Console.WriteLine("Disposing of old picture: " + sw.ElapsedMilliseconds + "ms");

                sw.Restart();
                Bitmap bmp = new Bitmap(outputStream);
                sw.Stop();
                Console.WriteLine("Converting stream to bitmap: " + sw.ElapsedMilliseconds + "ms");

                sw.Restart();
                framePictureBox.Image = bmp;
                sw.Stop();
                Console.WriteLine("Assigning to PictureBox: " + sw.ElapsedMilliseconds + "ms");

                sw.Restart();
                framePictureBox.Refresh();
                sw.Stop();
                Console.WriteLine("Rendering: " + sw.ElapsedMilliseconds + "ms");
            }));
        }
    }
}

测量

这是我的结果,对于 1 分 36 秒长的 480p 30fps 视频,在我的计算机上,我的平均结果是:

  • 读取ffmpeg发送的字节:110ms,峰值约为300ms
  • 处理前一帧:<1ms
  • 将流转换为位图:1ms
  • 将转换后的位图分配给 PictureBox:<1ms
  • 将 PictureBox 渲染到屏幕上:2ms

提醒一下,要达到 60 FPS,您需要渲染每一帧16.67ms, or 33.33ms30 FPS。

从中我们能明白什么

很明显,罪魁祸首是读取 ffmpeg 发送的流所花费的时间。

顺便说一句,我也尝试过@Charlieface的建议,但不,使用process.StandardOutput.BaseStream.CopyTo(outputStream);并没有节省太多时间。

事实上,你可以使用new Bitmap(process.StandardOutput.BaseStream);直接,但它并不会让整个事情变得更快。

真正的问题

乍一看,似乎很明显应该责怪 ffmpeg 将我们要求的帧转换为所需格式的速度很慢。

正如 @ChristophRackwitz 所指出的,这确实是代码缓慢的一个因素。

当向 ffmpeg 请求特定帧时,它实际上需要解码直到该帧的整个视频流,导致随着时间的推移,每个帧花费的时间越来越多。

不过,这对我来说不是问题,因为我使用的视频质量和帧率都很低。因此,我的测量是简约的,实际上,渲染每一帧所花费的时间可能比我测量的要长得多。我让你想象一下获取 1 小时长的 4K 144fps 视频的最后一帧需要多长时间。

还有另一个因素,它与流程创建有关。

事情是这样的,当你启动一个新的 ffmpeg 进程实例时,会发生以下情况:

  • Windows需要分配空间来启动新进程
  • ffmpeg 需要要求 Windows 打开您的视频文件
  • Windows 为 ffmpeg 提供文件句柄
  • ffmpeg 需要在完成转换后向 Windows 请求一个流来输出其数据
  • Windows 为 ffmpeg 提供了一个流来将其数据发送到
  • ffmpeg 终于可以完成它的工作并将其发送到流
  • ffmpeg 需要告诉 Windows 关闭视频文件,因为它已完成
  • Windows 关闭文件
  • ffmpeg 需要告诉 Windows 关闭它打开的流,因为它不再需要它
  • Windows 关闭流
  • ffmpeg需要告诉Windows它已经完成并且可以关闭
  • Windows 可以释放 ffmpeg 进程

这相当多了,对吧?

这里的问题是,在所有这些步骤中,只有一个步骤对于每个帧都是唯一的,那就是“ffmpeg 最终可以完成其工作并将其发送到流”步骤。

每一帧都会重复所有其他步骤,这需要花费大量时间。

Hence 我对你另一篇文章的回答 https://stackoverflow.com/a/76608543/9399492,我们不是为每个帧打开一个流,而是为所有帧打开一个流并实时渲染它们。这避免了为每个帧创建新的流程实例,也避免了必须从每个帧的开头解码整个视频流。

最终解决方案

您无法以当前的工作方式提高代码的速度,您需要从每帧一个进程切换为每个进程一个进程,这就是我对你另一篇文章的回答 https://stackoverflow.com/a/76608543/9399492 does.

谢谢阅读。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C#中使用ffmpeg提取帧时帧率慢且资源占用高 的相关文章

  • UML类图:抽象方法和属性是这样写的吗?

    当我第一次为一个小型 C 项目创建 uml 类图时 我在属性方面遇到了一些麻烦 最后我只是将属性添加为变量 lt
  • 为什么 Delphi 中的 ADO Next 记录处理速度变慢?

    我有一个多年前开发的 Delphi 4 程序 它使用Opus 直接访问 http sourceforge net projects directaccess 按顺序搜索 Microsoft Access 数据库并检索所需的记录 Delphi
  • 如何在列表框项目之间画一条线

    我希望能够用水平线分隔列表框中的每个项目 这只是我用于绘制项目的一些代码 private void symptomsList DrawItem object sender System Windows Forms DrawItemEvent
  • Newtonsoft JSON PreserveReferences处理自定义等于用法

    我目前在使用 Newtonsoft Json 时遇到一些问题 我想要的很简单 将要序列化的对象与所有属性和子属性进行比较以确保相等 我现在尝试创建自己的 EqualityComparer 但它仅与父对象的属性进行比较 另外 我尝试编写自己的
  • 为什么#pragma optimize("", off)

    我正在审查一个 C MFC 项目 在某些文件的开头有这样一行 pragma optimize off 我知道这会关闭所有以下功能的优化 但这样做的动机通常是什么 我专门使用它来在一组特定代码中获得更好的调试信息 并在优化的情况下编译应用程序
  • 如何将图像和 POST 数据上传到 Azure 移动服务 ApiController 终结点?

    我正在尝试上传图片and POST表单数据 尽管理想情况下我希望它是json 到我的端点Azure 移动服务应用 我有ApiController method HttpPost Route api upload databaseId sea
  • 加快网络抓取速度

    我正在使用一个非常简单的网络抓取工具抓取 23770 个网页scrapy 我对 scrapy 甚至 python 都很陌生 但设法编写了一个可以完成这项工作的蜘蛛 然而 它确实很慢 爬行 23770 个页面大约需要 28 小时 我看过scr
  • Qt moc 在头文件中实现?

    是否可以告诉 Qt MOC 我想声明该类并在单个文件中实现它 而不是将它们拆分为 h 和 cpp 文件 如果要在 cpp 文件中声明并实现 QObject 子类 则必须手动包含 moc 文件 例如 文件main cpp struct Sub
  • 指针减法混乱

    当我们从另一个指针中减去一个指针时 差值不等于它们相距多少字节 而是等于它们相距多少个整数 如果指向整数 为什么这样 这个想法是你指向内存块 06 07 08 09 10 11 mem 18 24 17 53 7 14 data 如果你有i
  • 如何返回 json 结果并将 unicode 字符转义为 \u1234

    我正在实现一个返回 json 结果的方法 例如 public JsonResult MethodName Guid key var result ApiHelper GetData key Data is stored in db as v
  • vector 超出范围后不清除内存

    我遇到了以下问题 我不确定我是否错了或者它是一个非常奇怪的错误 我填充了一个巨大的字符串数组 并希望在某个点将其清除 这是一个最小的例子 include
  • 如何衡量两个字符串之间的相似度? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 给定两个字符串text1 and text2 public SOMEUSABLERETURNTYPE Compare string t
  • 如何将单个 char 转换为 int [重复]

    这个问题在这里已经有答案了 我有一串数字 例如 123456789 我需要提取它们中的每一个以在计算中使用它们 我当然可以通过索引访问每个字符 但是如何将其转换为 int 我研究过 atoi 但它需要一个字符串作为参数 因此 我必须将每个字
  • 当操作繁忙时,表单不执行任何操作(冻结)

    我有一个使用 C 的 WinForms 应用程序 我尝试从文件中读取一些数据并将其插入数据表中 当此操作很忙时 我的表单冻结并且无法移动它 有谁知道我该如何解决这个问题 这可能是因为您在 UI 线程上执行了操作 将文件和数据库操作移至另一个
  • 如何让Gtk+窗口背景透明?

    我想让 Gtk 窗口的背景透明 以便只有窗口中的小部件可见 我找到了一些教程 http mikehearn wordpress com 2006 03 26 gtk windows with alpha channels https web
  • mysql-connector-c++ - “get_driver_instance”不是“sql::mysql”的成员

    我是 C 的初学者 我认为学习的唯一方法就是接触一些代码 我正在尝试构建一个连接到 mysql 数据库的程序 我在 Linux 上使用 g 没有想法 我运行 make 这是我的错误 hello cpp 38 error get driver
  • 防止索引超出范围错误

    我想编写对某些条件的检查 而不必使用 try catch 并且我想避免出现 Index Out of Range 错误的可能性 if array Element 0 Object Length gt 0 array Element 1 Ob
  • 使用 libcurl 检查 SFTP 站点上是否存在文件

    我使用 C 和 libcurl 进行 SFTP FTPS 传输 在上传文件之前 我需要检查文件是否存在而不实际下载它 如果该文件不存在 我会遇到以下问题 set up curlhandle for the public private ke
  • 使用按位运算符相乘

    我想知道如何使用按位运算符将一系列二进制位相乘 但是 我有兴趣这样做来查找二进制值的十进制小数值 这是我正在尝试做的一个例子 假设 1010010 我想使用每个单独的位 以便将其计算为 1 2 1 0 2 2 1 2 3 0 2 4 虽然我
  • 恢复上传文件控制

    我确实阅读了以下帖子 C 暂停 恢复上传 https stackoverflow com questions 1048330 pause resume upload in c 使用 HTTP 恢复上传 https stackoverflow

随机推荐

  • 如何在cmake中使用调试符号构建依赖共享库?

    我的代码是这样组织的 cpp main cpp 从调用代码dataStructures and common CMakeLists txt topmostCMakeLists 文件 build common CMakeLists txt 应
  • Android Java - 创建 Cronjob

    我想要制作一个在后端运行的 Cronjob 并启动一个方法 30 分钟 如果函数返回 true 或其他 Cronjob 将创建一个状态栏通知 在 Android 中这可能吗 如果是的话 用哪个函数 非常感谢 安卓系统报警管理器 http d
  • 如何让 CreateProcess/CreateProcessW 在路径 > MAX_PATH 字符中执行进程

    我试图让 CreateProcess 或 CreateProcessW 执行名称 http msdn microsoft com en us library ms682425 aspx http msdn microsoft com en
  • 限制可排序的容器/父级

    好的 我又来了 和 RubaXa 一起玩Sortable http rubaxa github io Sortable 插件 希望他就在这附近 因为这个插件相当复杂 一些发现 我花了一些时间才完全理解这个机制 但我认为我是对的 Case 1
  • Windows 命令提示符中的别名

    我已经添加了notepad exe到我的环境变量中的路径 现在在命令提示符下 notepad exe filename txt打开filename txt 但我想做的只是np filename txt打开文件 我尝试使用DOSKEY np
  • intel avx2 中是否有 movemask 指令的逆指令?

    movemask 指令采用 m256i 并返回 int32 其中每个位 前 4 8 或所有 32 位 具体取决于输入向量元素类型 是相应向量元素的最高有效位 我想做相反的事情 取 32 其中只有 4 8 或 32 个最低有效位有意义 并获得
  • 冒泡排序有什么用? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 如何自定义App Designer人物的背景?

    我想附加徽标或更改应用程序设计器的整个背景uifigure 如何才能做到这一点 如果你想设置一个整个图的纯色背景色 那里存在有记录的方式 https www mathworks com help matlab ref uifigureapp
  • 验证在部分视图中不起作用

    我有一个索引页面 其中有两个部分视图 登录和注册 我正在使用数据模型验证 登录 cshtml model Project ViewModel UserModel div using Html BeginForm Login account
  • 从 Ada 访问 c 常量

    我有一个带有这样类型定义的头文件 ifndef SETSIZE define SETSIZE 32 endif typedef struct set unsigned array SETSIZE set t 要使用相应的 C 函数 我需要在
  • jquery 获取之前输入的文本

    我有以下 html div class active string div
  • 将大文件作为流发送到 process.getOutputStream

    我在 Windows 机器中使用 gzip 实用程序 我压缩了一个文件并作为 blob 存储在数据库中 当我想使用 gzip 实用程序解压缩此文件时 我将此字节流写入 process getOutputStream 但超过30KB后 就无法
  • Android 绘制点

    如何用画布绘制完整的圆或点 我使用画布和路径 绘画类 my code Override public boolean onTouchEvent MotionEvent event float eventX event getX float
  • 如何向谷歌图表中的图例添加工具提示

    使用最新版本的 Google Charts API 我有一个简单的条形图 我想在将鼠标悬停在图例中的元素上时显示一个工具提示 解释图例中的每个项目是什么 我仍然希望栏上的工具提示保持不变并显示其标签和值
  • 使用 GSM 调制解调器接收短信

    我读到 GSM 调制解调器每分钟最多只能接收 30 条短信 如果您需要收到更多 您会怎么做 还有其他技术吗 我认为您可能想要与列出的答案不同的东西构建短信服务器的最佳实践是什么 https stackoverflow com questio
  • 多态关联

    如果您具有多态belongs to关联 那么引用将添加所需的两列 create table products do t t references attachment polymorphic gt default gt Photo end
  • 我应该为范围最小查询使用什么使用 O(n) 存储和 O(log n) 查询时间的数据结构?

    我被算法课的以下作业问题难住了 Suppose that we are given a sequence of n values x1 x2 xn and seek to quickly answer repeated queries of
  • 鲍尔畸形

    我正在学习如何使用 Bower 为了开始 我创建了一个基本的 Bower json 文件 其职责是获取 jquery 我的 Bower json 文件如下所示 name MyProject version 0 0 1 devDependen
  • python 中的私有公共受保护访问说明符

    我们可以在Python中模拟私有和受保护的访问说明符吗 名称修改 eg var 10 可以模拟私有 但可以通过对象轻松地从外部访问 object className var 那么有没有一种方法可以模拟 或者 python 是否直接是我不知道
  • C#中使用ffmpeg提取帧时帧率慢且资源占用高

    我目前正在开发一个项目 需要在 C 中使用 ffmpeg 从视频中提取帧 但是 我面临帧速率慢和资源使用率高的问题 我使用的代码如下 private bool move false private int master frame 0 pr