【问题标题】:How to maintain stable frames-per-second while capturing real-time video in C#如何在 C# 中捕获实时视频时保持稳定的每秒帧数
【发布时间】:2013-08-15 00:15:19
【问题描述】:

我编写了一个 C# 程序,该程序通过相机制造商的专有 API 从专用相机捕获视频。我可以通过 FileStream 对象将捕获的帧写入磁盘,但在帧速率方面我受制于相机和磁盘 I/O。

确保以所需帧速率写入磁盘的最佳方法是什么?是否有某种算法可以计算实时平均帧率,然后添加/丢弃帧以保持某个所需的帧率?

【问题讨论】:

    标签: c# video-capture


    【解决方案1】:

    由于缺乏信息,很难说清楚。

    格式是什么?有压缩吗?

    相机 API 如何发送帧?它们是否定时,因此相机会发送您要求的帧速率?如果是这样,那么您实际上是在处理 I/O 速度。

    如果您需要高质量,并且在没有压缩的情况下编写,您可以尝试一些无损压缩算法来平衡处理和驱动 I/O。如果瓶颈在于高驱动器 I/O,您可以获得一些速度。

    对于框架,有一些方法可以实现。通常帧都有时间戳,你应该搜索它,并丢弃那些离另一个帧很近的帧。

    假设你想要 60 fps,所以帧之间的毫秒间隔是 1000/60=16ms,如果你得到的帧在最后一个之后有 13ms 的时间戳,你可以丢弃它并且不要写入磁盘。

    【讨论】:

    • 我正在写出原始帧。没有特殊格式,也没有压缩。我正在循环查询相机。我相信瓶颈确实是 I/O,它可能因计算机而异。我需要知道我需要做什么样的计算来保持 fps 不变。
    • 你在开玩笑——我说 I/O 会有所不同? :-) 如果你不使用压缩可以开始使用,使用无损压缩算法,最简单的可以达到很好的效果,不会丢失你的原始数据,所以你的 I/O 传输会这样减少。我使用高清摄像机,即使是 720p,我也需要单独的高清来保留 I/O。对于框架有一些方法。通常帧有时间戳,你应该搜索它,并丢弃那些离另一个如此近的帧。这只是一种方式。
    • 我别无选择。我需要原始的、未压缩的帧。
    • 我不这么认为,因为压缩可以是简单的行程压缩,而不是 MPEG 或有损压缩。无论如何,我编辑了我的答案,让您了解如何根据您想要的帧速率丢弃您“需要”的帧。但是如果限制帧率还不够,你有两个选择:使用压缩,或者去购买硬件、RAID,然后继续。
    • 限制帧率正是我想要的。例如,我正在从相机捕捉并以 60 fps 的速度录制,但我不想以这种速度更新 UI。我只想以 20 fps 左右的速度更新 UI。现在我处于一个循环中,我会尽可能快地捕捉到循环允许的速度。我希望能够以指定的帧速率限制它,即使我可以捕捉得比这更快。如果磁盘 I/O 太慢,那么我需要丢帧。但我需要知道如何计算。
    【解决方案2】:

    在理想情况下,您会检查第一秒,它会为您提供系统支持的每秒帧数。

    假设您的相机正在捕获 60 fps,但您的计算机实际上只能处理 45 fps。您必须做的是每秒跳过总共 15 帧才能跟上。到这里,就很简单了。

    这个基本情况的数学是:

    60 / 15 = 4
    

    因此,每四个传入帧跳过一帧,就像这样(保留标有X 的帧,跳过其他帧):

    000000000011111111112222222222333333333344444444445555555555
    012345678901234567890123456789012345678901234567890123456789
    
    XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
    

    当然,您很可能没有得到如此明确的案例。数学原理保持不变,只是您最终会在不同的时间点跳帧:

    // simple algorithm based on fps from source & destination
    // which fails to keep up long term
    double camera_fps = 60.0;
    double io_fps = camera_fps;
    
    for(;;)
    {
      double frames_step = camera_fps / io_fps;
    
      double start_time = time(); // assuming you get at least ms precision
      double frame = 0.0;
      for(int i(0); i < camera_fps; ++i)
      {
        buffer frame_data = capture_frame();
        if(i == (int)frame)  // <- part of the magic happens here
        {
          save_frame(frame_data);
        }
        frame += frames_step;
      }
    
      double end_time = time();
      double new_fps = camera_fps / (end_time - start_time);
    
      if(new_fps < io_fps)
      {
        io_fps = new_fps;
      }
    }
    

    正如该算法所示,您希望随着时间的推移调整 fps。在第一秒,很可能会因为各种原因给你一个无效的结果。例如,写入磁盘可能会被缓冲,所以速度非常快,看起来好像可以支持 60 fps。稍后,fps 会变慢,您可能会发现您的最大 I/O 速度改为 57 fps。

    这种基本算法的一个问题是,您可以轻松减少帧数以使其在 1 秒内工作,但它只会降低 fps(即我仅在 new_fps 较小时更新 io_fps)。如果您找到io_fps 的正确号码,那就没问题了。如果你走得太远,你会在不应该的时候丢帧。这是因为new_fps 将是 1.0,而 (end_time - start_time) 正好是 1 秒,这意味着您没有花费太多时间来捕获和保存传入的帧。

    解决此问题的方法是为您的save_frame() 函数计时。如果在内循环内的总时间少于 1 秒,那么您可以增加可以节省的帧数。如果您可以使用两个线程,这会更好。一个线程读取帧,将帧推送到内存 FIFO 中,另一个线程从该 FIFO 中检索帧。这意味着捕获一帧的时间量不会(太多)影响保存一帧所需的时间。

    bool stop = false;
    
    // capture
    for(;;)
    {
      buffer frame_data = capture_frame();
      fifo.push(frame_data);
      if(stop)
      {
        break;
      }
    }
    
    // save to disk
    double expected_time_to_save_one_frame = 1.0 / camera_fps;
    double next_start_time = time();
    for(;;)
    {
      buffer frame_data = fifo.pop();
      double start_time = next_start_time;
      save_frame(frame_data);
      next_start_time = time();
      double end_time = time();
      if(start_time - end_time > expected_time_to_save_one_frame)
      {
        fifo.pop();  // skip one frame
      }
    }
    

    这只是伪代码,我可能犯了一些错误。它还预计start_timeend_time 之间的差距不会超过一帧(即,如果您遇到捕获为 60 fps 并且 I/O 支持小于 30 fps 的情况,您通常会必须连续跳过两帧)。

    对于压缩帧的人,请记住,一次调用save_frame() 的时间会有很大差异。有时,框架很容易被压缩(没有水平移动),有时它真的很慢。这就是这种活力可以提供巨大帮助的地方。假设您在录制时不会发生太多其他事情,您的磁盘 I/O 在达到支持的最大速度后应该不会有太大变化。

    重要提示:这些算法假设相机 fps 是固定的;这可能并不完全正确;您还可以为相机计时并相应地调整camera_fps 参数(这意味着expected_time_to_save_one_frame 变量也可以随时间变化)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-07-20
      • 2017-04-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-07
      • 2015-05-27
      相关资源
      最近更新 更多