用circular_buffer實現的播放快取佇列
背景
在我們的一個專案中,開音視訊會議時,音訊比視訊慢了將近一秒,由於歷史問題,會議伺服器沒法進行改動,所以要求在解碼端做這樣一個相容處理,主動快取視訊達到將視訊延時播放的目的,從而實現音視訊的同步。延時的大小可配。
解碼播放的基本流程
整個視訊解碼端的流程如下:
在這個流程中,rtp模組與JitterBuffer模組是序列的。JitterBuffer模組與解碼模組是非同步關係,解碼模組與渲染模組是序列關係。加入快取佇列後的流程圖如下
加入快取佇列後,解碼模組與渲染模組即是非同步關係了。
基於circular_buffer的實現
在我前面的文章介紹過circular_buffer的特性,其相比stl中的deque,vector實現的快取佇列效率要高,實現簡潔。程式碼如下:
template <class T>
class DisplayQeque :public boost::noncopyable
{
public:
typedef boost::circular_buffer<T> BufferType;
typedef typename BufferType::size_type size_type;
typedef typename BufferType::value_type value_type;
//建構函式指定佇列的大小
explicit DisplayQeque(size_type size) :m_Qeque (size), m_iUnread(0), m_iSize(size)
{}
void Put(const value_type &data)
{
std::unique_lock<std::mutex> lock(m_mutex);
m_not_full.wait(lock, std::bind(&DisplayQeque<value_type>::is_can_put, this));
m_Qeque.push_front(data);
++m_iUnread;
if (m_Qeque.full())
{
lock.unlock();
m_not_empty.notify_one();
}
}
void Get(value_type &data)
{
std::unique_lock<std::mutex> lock(m_mutex);
m_not_empty.wait(lock, std::bind(&DisplayQeque<value_type>::is_can_get, this));
data = m_Qeque[--m_iUnread];
if (m_iUnread < m_iSize)
{
lock.unlock();
m_not_full.notify_one();
}
}
void Clear()
{
std::lock_guard<std::mutex> lock(m_mutex);
m_Qeque.clear();
}
value_type Back()
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_Qeque.front();
}
int Size()
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_Qeque.size();
}
private:
bool is_can_get() const { return m_iUnread == m_iSize; }
bool is_can_put() const { return m_iUnread < m_iSize; }
private:
BufferType m_Qeque;
std::mutex m_mutex;
std::condition_variable m_not_empty;
std::condition_variable m_not_full;
size_type m_iUnread;
size_type m_iSize;
};
基本流程如下:
1. 在解碼執行緒中呼叫Put函式將解碼後的一幀資料放入佇列,在渲染執行緒中呼叫Get函式取出一幀資料後進行渲染。該佇列的機制是渲染執行緒在佇列滿時才開始取資料,佇列滿了以後會在解碼執行緒每放入一幀資料後,渲染執行緒才取資料,佇列大小始終維持指定的大小,從而達到延遲渲染的目的。
2. 佇列的size是指定快取的幀數,在該佇列的實現機制下,渲染的幀率是等同於解碼的幀率,那麼在配置快取的資料,可以根據解碼的幀率來估算快取的秒數,比如解碼幀率為25fps,那麼將size指定為100時(快取100幀資料),即表示為快取了4秒的資料。
實現剖析
在佇列的實現中有一個下標成員變數m_iUnread,取出資料是通過m_iUnread去取,這樣做有個好處是不用顯式的呼叫pop_back函式去主動刪除已取元素。在circular_buffer的內部實現中,當佇列滿時會直接以覆蓋記憶體的方式去實現刪除,從而提高效率,這樣不使用pop_back也免去了一次物件的析構。
voip中的音視訊同步
在voip系統中,作為解碼端是應該加入音視訊同步機制的,在解碼端用rtcp中的ntp時間來判斷音視訊流的絕對時間,從而進行同步,但是有個最基本的條件是,在傳送端傳送音視訊流時,必須保證音視訊是同一個會話中的並且保證打包時的時間戳基準線是相同的。如果傳送端無法保證這兩點,那麼在解碼端通過時間戳是無法進行同步的。也只能通過如上的估算機制快取資料進行同步了。
以上。