三、緩衝區的動態分配與實現。
基於這樣的一個事實,音頻資料遠遠小於視頻資料。所以在這裡把音頻緩衝區與視頻緩衝區分開管理。並且根據統計,同一媒體流的RTP資料包大小相近,比如,音頻大概幾百個Byte,視頻1.3kByte。對視頻緩衝區,我們分為兩部分。一部分為正在使用資料區,一部分為空白閑區。當RTP接收到資料後,就從空閑區取得一個Memory塊。這個塊的大小大於或等於接收到的資料與Packet頭部之和。取得Memory塊後,將資料拷貝進來,然後根據幀序號和包序號,插入到正在使用資料區,供播放器調用。播放器在正在使用資料區取得一幀資料,解碼,播放。使用完後該資料區塊並不立即釋放,而是有序的插入到空閑資料區。
這樣的Memory重複利用思想要用到上節中介紹的指標數組和指標隊列。指標資料用來管理空閑資料區塊鏈。實現按大小有序插入和尋找。指標隊列用來管理排序之後的資料幀,按幀序號和包序號排放。我們這裡將一個視訊框架分成若干個RTP包。
首先,定義CMyPtrArray m_FreeList,用來管理空閑資料有序鏈。
在空閑區申請Memory塊的方法如下:
ppFrameBuf 為返回資料區塊指標,FRAME_BUFFER是一個虛擬結構,包括Buffer實際長度和指向Buffer的指標和其他自訂成員,nLen為申請大小。
void GetOneFreeBuf(FRAME_BUFFER **ppFrameBuf,DWORD nLen)
{
if(NULL == ppFrameBuf)
return ;
FRAME_BUFFER *pFrameBuf = NULL;
//此處添加互斥代碼
if(m_FreeList.GetSize() > 0)
{
int nStart = 0,nEnd = m_FreeList.GetSize();
int nCur = (nStart + nEnd) / 2;
FRAME_BUFFER *pTmpBuf = NULL;
while(1)
{
pTmpBuf = (FRAME_BUFFER *)m_FreeList.GetAt(nCur);
if((pTmpBuf->nBufSize >= nLen) && ((pTmpBuf->nBufSize - nLen) <= 100))
break;
else if(pTmpBuf->nBufSize > nLen)
nEnd = nCur;
else
nStart = nCur;
nCur = (nStart + nEnd) / 2;
if(nCur == nStart || nCur == nEnd)
{
pTmpBuf = (FRAME_BUFFER *)m_FreeList.GetAt(nCur);
break;
}
}
if(nCur >= m_FreeList.GetSize())
nCur = m_FreeList.GetSize() -1;
if(pTmpBuf->nBufSize < nLen && nCur < (m_FreeList.GetSize() - 1))
{
nCur ++;
pTmpBuf = (FRAME_BUFFER *)m_FreeList.GetAt(nCur);
}
if(pTmpBuf->nBufSize >= nLen)
{
pFrameBuf = pTmpBuf;
//
m_FreeList.RemoveAt(nCur);
}
}
*ppFrameBuf = NULL;
if(NULL == pFrameBuf)
{
pFrameBuf = new FRAME_BUFFER;
if(NULL == pFrameBuf)
{
return;
}
pFrameBuf->nBufSize = nLen;
pFrameBuf->pFrameData = new BYTE[nLen + 100];
if(NULL == pFrameBuf->pFrameData)
{
delete pFrameBuf;
return ;
}
}
// TRACE("CStreamBuffer::GetOneFreeBuf,NeedSize:%d,ActualSize:%d/n",nLen,pFrameBuf->nBufSize);
*ppFrameBuf = pFrameBuf;
}
釋放Memory塊到空閑去的方法如下:
void ReleaseBuf(FRAME_BUFFER *pFrameBuf)//從小到大排序
{
if(NULL == pFrameBuf || NULL == pFrameBuf->pFrameData || 0 == pFrameBuf->nBufSize)
return ;
FRAME_BUFFER *pTmpBuf = NULL;
//添加互斥代碼
DWORD nCur = 0;
if(m_FreeList.GetSize() > 0)
{
DWORD nStart = 0,nEnd = m_FreeList.GetSize();
nCur = (nStart + nEnd) / 2;
while(1)
{
pTmpBuf = (FRAME_BUFFER *)m_FreeList.GetAt(nCur);
if(pTmpBuf->nBufSize == pFrameBuf->nBufSize)
break;
else if(pTmpBuf->nBufSize > pFrameBuf->nBufSize)
nEnd = nCur;
else
nStart = nCur;
nCur = (nStart + nEnd) / 2;
if(nCur == nStart || nCur == nEnd)
break;
}
if(nCur > 0 && pTmpBuf->nBufSize > pFrameBuf->nBufSize)
nCur --;
}
m_FreeList.InsertAt(nCur,pFrameBuf);
}
當然程式退出時,要記得釋放所有申請的空間。
四、緩衝區設計
有了前面的技術基礎。我們就可以從容的設計自己的緩衝區了。因為音頻資料比視頻資料小一個數量級。所以,可以將一個音訊框架打包為一個RTP包。而將一個視訊框架打包為多個RTP包。對應的,在用戶端。我們收到一個RTP音頻包可以直接送入音訊框架隊列,供播放器使用。收到RTP視頻包,首先要組成完整幀,然後供播放器使用。緩衝區如下: