三、缓存区的动态分配与实现。
基于这样的一个事实,音频数据远远小于视频数据。所以在这里把音频缓冲区与视频缓冲区分开管理。并且根据统计,同一媒体流的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视频包,首先要组成完整帧,然后供播放器使用。缓冲区示意图如下: