在将帧保存到磁盘时防止丢帧
Prevent frame dropping while saving frames to disk
我正在尝试编写C++代码,将传入的视频帧保存到磁盘。异步到达的帧由生产者线程推送到队列中。帧由使用者线程从队列中弹出。生产者和消费者的相互排斥是使用互斥锁完成的。但是,我仍然注意到帧被丢弃。丢弃的帧(可能)对应于生产者尝试将当前帧推送到队列但由于使用者持有锁而无法这样做的情况。有什么建议吗?我基本上不希望制片人等待。等待消费者对我来说是可以的。
EDIT-0 :不涉及锁定的替代想法。这行得通吗?
- 制作人最初排队
n
秒的视频。n
可以是帧速率的一小部分倍数。 - 只要队列包含
>= n
秒的视频,使用者就会逐帧取消排队并保存到磁盘。 - 视频完成后,队列将刷新到磁盘。
编辑-1:帧到达~15 fps。
编辑-2:代码概述:
主驱动程序代码
// Main function
void LVD::DumpFrame(const IplImage *frame)
{
// Copies frame into internal buffer.
// buffer object is a wrapper around OpenCV's IplImage
Initialize(frame);
// (Producer thread) -- Pushes buffer onto queue
// Thread locks queue, pushes buffer onto queue, unlocks queue and dies
PushBufferOntoQueue();
// (Consumer thread) -- Pop off queue and save to disk
// Thread locks queue, pops it, unlocks queue,
// saves popped buffer to disk and dies
DumpQueue();
++m_frame_id;
}
void LVD::Initialize(const IplImage *frame)
{
if(NULL == m_buffer) // first iteration
m_buffer = new ImageBuffer(frame);
else
m_buffer->Copy(frame);
}
制作人
void LVD::PushBufferOntoQueue()
{
m_queingThread = ::CreateThread( NULL, 0, ThreadFuncPushImageBufferOntoQueue, this, 0, &m_dwThreadID);
}
DWORD WINAPI LVD::ThreadFuncPushImageBufferOntoQueue(void *arg)
{
LVD* videoDumper = reinterpret_cast<LVD*>(arg);
LocalLock ll( &videoDumper->m_que_lock, 60*1000 );
videoDumper->m_frameQue.push(*(videoDumper->m_buffer));
ll.Unlock();
return 0;
}
消费者
void LVD::DumpQueue()
{
m_dumpingThread = ::CreateThread( NULL, 0, ThreadFuncDumpFrames, this, 0, &m_dwThreadID);
}
DWORD WINAPI LVD::ThreadFuncDumpFrames(void *arg)
{
LVD* videoDumper = reinterpret_cast<LVD*>(arg);
LocalLock ll( &videoDumper->m_que_lock, 60*1000 );
if(videoDumper->m_frameQue.size() > 0 )
{
videoDumper->m_save_frame=videoDumper->m_frameQue.front();
videoDumper->m_frameQue.pop();
}
ll.Unlock();
stringstream ss;
ss << videoDumper->m_saveDir.c_str() << "\";
ss << videoDumper->m_startTime.c_str() << "\";
ss << setfill('0') << setw(6) << videoDumper->m_frame_id;
ss << ".png";
videoDumper->m_save_frame.SaveImage(ss.str().c_str());
return 0;
}
注意:
(1) 我不能使用 C++11。因此,Herb Sutter的DDJ文章不是一种选择。
(2)我发现了对无限单一生产者-消费者队列的引用。但是,作者指出排队(添加帧)可能不是没有等待的。
(3)我还找到了liblfds,一个C库,但不确定它是否能达到我的目的。
不可能是问题。 视频帧以 16 毫秒的间隔到达,最坏的情况是。 队列只需要存储指向帧的指针。 以线程安全的方式添加/删除一个永远不会超过一微秒。
您需要寻找另一种解释和解决方案。 视频确实永远存在消防水带问题。 磁盘驱动器通常不够快,无法跟上未压缩的视频流。 因此,如果您的消费者无法跟上生产者的步伐,那么就会有一些东西。 使用丢帧时,当您(正确)阻止队列无限制地增长时,可能出现的结果。
请务必考虑对视频进行编码。 提供实时 MPEG 和 AVC 编码器。 在他们压缩流后,您应该没有问题跟上磁盘。
循环缓冲区绝对是一个不错的选择。如果你让它使用 2^n 大小,你也可以使用这个技巧来更新指针:
inline int update_index(int x)
{
return (x + 1) & (size-1);
}
这样,就不需要使用昂贵的比较(和相应的跳转)或除法(任何处理器中最昂贵的整数操作 - 不包括"填充/复制大块内存"类型的操作)。
在处理视频(或一般图形)时,必须进行"缓冲区管理"。通常,这是跟踪"帧缓冲区"状态并避免复制不必要的内容的情况。
典型的方法是分配 2 或 3 个视频缓冲区(或帧缓冲区,或您所说的)。缓冲区可以由生产者或使用者拥有。转让只是所有权。因此,当视频驱动程序发出"此缓冲区已满"的信号时,所有权现在属于消费者,消费者将读取缓冲区并将其存储到磁盘 [或其他任何内容]。存储完成后,缓冲区被返回("释放"),以便生产者可以重用它。将数据从缓冲区中复制出来是昂贵的 [需要时间],因此除非绝对必要,否则您不想这样做。
- 在全局变量中保存类的实例以重新创建类(创建"backup")
- 如何在选项卡视图Qt中设置一个新项目,并保存以前的项目
- 如何使用OpenCV将RBG图像转换为HSV,并将H、S和V值保存为C++中的3个独立图像
- 如何将二进制格式的 C++ 对象的 std::vector 保存到磁盘?
- 如何将 IPropertyBag 保存到磁盘
- 在 Visual Studio 中调试时,是否可以将一些 C/C++ 结构内容保存到磁盘?
- 使用C++项目将数据保存在磁盘上
- 在远程服务器上接收 Gstreamer 流,并保存到磁盘
- 独立 exe:磁盘上保存配置文件和日志的位置
- 使用 C++ 将程序集保存到磁盘
- 协议缓冲区;将数据保存到磁盘并重新加载问题
- 将字符串向量保存到磁盘不起作用
- C++ 序列化 std::error_code 以便通过网络传输或保存到磁盘
- 增强将未定义的映射保存到磁盘的能力
- 将数据结构c++保存到磁盘
- 如果机器立即崩溃,那么fflush()在将数据保存到磁盘方面是否与fclose()一样好?
- 将UILabel的内容转换为图像并将其保存到磁盘
- 如何在Matlab中训练一个模型,将其保存到磁盘,并在c++中加载程序
- 使用FMOD将输出保存到磁盘
- 在将帧保存到磁盘时防止丢帧