2

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

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

4

2 回答 2

0

由于缺乏信息,很难说太多。

格式是什么?有压缩吗?

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

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

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

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

于 2013-08-15T01:08:25.560 回答
0

在一个完美的世界中,您会检查第一秒,它会为您提供系统支持的每秒帧数。

假设您的相机正在捕获 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(即我io_fps只在new_fps更小的时候更新)。如果您找到 的正确数字io_fps,那就没问题了。如果你走得太远,你会在不应该的时候丢帧。这是因为恰好是 1 秒new_fps时将是 1.0 (end_time - start_time),这意味着您没有花费太多时间来捕获和保存传入的帧。

此问题的解决方案是为您的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_time和之间的差距end_time不会超过一帧(即,如果您遇到捕获为 60 fps 并且 I/O 支持小于 30 fps 的情况,您通常必须跳过两帧一排)。

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

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

于 2022-01-10T00:39:40.107 回答