1

我正在尝试编写将传入的视频帧保存到磁盘的 C++ 代码。异步到达的帧由生产者线程推送到队列中。消费者线程将帧从队列中弹出。生产者和消费者的相互排斥是使用互斥体完成的。但是,我仍然注意到帧被丢弃。丢弃的帧(可能)对应于生产者尝试将当前帧推入队列但由于消费者持有锁而无法这样做的实例。有什么建议么 ?我基本上不希望制片人等待。等待消费者对我来说没问题。

EDIT-0:不涉及锁定的替代想法。这行得通吗?

  1. Producer 最初将n几秒钟的视频排入队列。n可以是帧速率的一些小的倍数。
  2. 只要队列包含>= n几秒钟的视频,消费者就会逐帧出列并保存到磁盘。
  3. 视频完成后,队列被刷新到磁盘。

EDIT-1:帧以约 15 fps 的速度到达。

EDIT-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 库,但不确定它是否能达到我的目的。

4

2 回答 2

4

队列不可能是问题。视频帧以 16 毫秒的间隔到达,最坏的情况。您的队列只需要存储一个指向帧的指针。以线程安全的方式添加/删除一个永远不会超过一微秒。

您需要寻找其他解释和解决方案。视频确实永远存在消防水管问题。磁盘驱动器的速度通常不足以跟上未压缩的视频流。因此,如果您的消费者无法跟上生产者的步伐,那么就会有所作为。当您(正确地)阻止队列无限制地增长时,可能会出现丢帧的结果。

请务必考虑对视频进行编码。提供实时 MPEG 和 AVC 编码器。在他们压缩流之后,您应该不会有跟上磁盘的问题。

于 2013-01-23T00:08:43.347 回答
0

圆形缓冲区绝对是一个不错的选择。如果你让它使用 2^n 大小,你也可以使用这个技巧来更新指针:

inline int update_index(int x) 
{
   return (x + 1) & (size-1);
}

这样,就不需要使用昂贵的比较(和相应的跳转)或除法(任何处理器中单个最昂贵的整数操作 - 不计算“填充/复制大块内存”类型的操作)。

在处理视频(或一般的图形)时,必须进行“缓冲区管理”。通常,这是跟踪“帧缓冲区”状态并避免复制不必要的内容的情况。

典型的方法是分配 2 或 3 个视频缓冲区(或帧缓冲区,或您所说的)。缓冲区可以由生产者或消费者拥有。转让只是所有权。因此,当视频驱动程序发出“此缓冲区已满”的信号时,所有权现在属于消费者,消费者将读取缓冲区并将其存储到磁盘 [或其他]。当存储完成时,缓冲区被归还(“释放”),以便生产者可以重新使用它。将数据复制出缓冲区是昂贵的 [需要时间],因此除非绝对必要,否则您不想这样做。

于 2013-01-22T23:29:52.087 回答