在将帧保存到磁盘时防止帧丢失

时间:2013-01-22 23:14:13

标签: c++ multithreading video-streaming message-queue

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

EDIT-0:不涉及锁定的替代想法。这有用吗?

  1. 制作人最初将n秒的视频排队。 n可以是帧速率的一小部分。
  2. 只要队列包含>= n秒的视频,消费者就会逐帧出列并保存到磁盘。
  3. 视频完成后,队列将刷新到磁盘。
  4. 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's DDJ article不是一种选择。

    (2)我找到了一个reference到一个无界的单一生产者 - 消费者队列。但是,作者声明入队(添加帧)可能不是等待的。

    (3)我还找到了liblfds,一个C库,但不确定它是否符合我的目的。

2 个答案:

答案 0 :(得分:4)

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

您需要寻找其他解释和解决方案。视频确实存在火灾问题。磁盘驱动器通常不够快,无法跟上未压缩的视频流。因此,如果您的消费者无法跟上生产者,那么就会有所作为。如果您(正确地)阻止队列无限制地增长,那么使用丢帧可能会产生结果。

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

答案 1 :(得分:0)

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

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

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

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

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