我编写了一个 C# 程序,该程序通过相机制造商的专有 API 从专用相机捕获视频。我可以通过 FileStream 对象将捕获的帧写入磁盘,但在帧速率方面我受制于相机和磁盘 I/O。
确保以所需帧速率写入磁盘的最佳方法是什么?是否有某种可用的算法可以计算实时平均帧率,然后添加/丢弃帧以保持某个所需的帧率?
我编写了一个 C# 程序,该程序通过相机制造商的专有 API 从专用相机捕获视频。我可以通过 FileStream 对象将捕获的帧写入磁盘,但在帧速率方面我受制于相机和磁盘 I/O。
确保以所需帧速率写入磁盘的最佳方法是什么?是否有某种可用的算法可以计算实时平均帧率,然后添加/丢弃帧以保持某个所需的帧率?
由于缺乏信息,很难说太多。
格式是什么?有压缩吗?
相机 API 如何发送帧?它们是否定时,因此相机会发送您要求的帧速率?如果是这样,那么您实际上是在处理 I/O 速度。
如果您需要高质量,并且在没有压缩的情况下编写,您可以尝试一些无损压缩算法来平衡处理和驱动 I/O。如果瓶颈在于高驱动器 I/O,您可以获得一些速度。
对于框架,有一些方法可以实现。通常帧有时间戳,你应该搜索它,并丢弃那些离另一个如此近的帧。
假设你想要 60 fps,所以帧之间的毫秒空间是 1000/60=16ms,如果你得到的帧在最后一帧之后有 13ms 的时间戳,你可以丢弃它而不写入磁盘。
在一个完美的世界中,您会检查第一秒,它会为您提供系统支持的每秒帧数。
假设您的相机正在捕获 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
变量也可以随时间变化)。