转自:http://www.cnblogs.com/lidabo/p/6604988.html
1、写在开始之前:
最近因为新工作要维护别人留下的GB模块代码,先熟悉了流程,然后也试着封装了下ps流,结果也能通过测试正常预览了,当然,其中开发读文档的头疼,预览花屏,卡帧的事情都有遇到,当时慢慢的看文档,整理逻辑,也就都顺利解决了,下面把大致的一些流程代码贴出来分享下。既然是对接国标,自然少不了通读它的标准文档和相关的RFC文档了!具体的我就不说了,可以用百度google下的。
注意:因为是GB要求ps封装后再加上rtp头的格式来的, 所以下面代码中我也加上了rtp头,如果不需要的话,直接屏蔽代码中的rtp即可。
2、封装的重点
当我们从读缓冲区中取得一帧音视频数据的时候,封装时其实每一帧数据有且只有一个ps头和psm头,如果是I帧的话,就还多一个system头,一个或者多个pes头和rtp头,
像如果帧数据过长的话,就得进行分片,每片都会包含一个pes头,rtp负载最好长度1460,所以会进行再分包操作!所以每一个包数据至少一个rtp+databuf,每一片数据,至少有个rtp+pes+databuf,每一帧数据至少有rtp+ps+psm+pes+databuf(关键帧的话:多一个system头)
3、具体的各个封装的代码实现
首先给去一个整体的封装rtp->ps->sys->psm->pes(如果只要ps的话,则为ps->sys->psm->pes)的大致流程,
然后再一一罗列出各个部件的封装接口
/***
*@remark: 音视频数据的打包成ps流,并封装成rtp
*@param : pData [in] 需要发送的音视频数据
* nFrameLen [in] 发送数据的长度
* pPacker [in] 数据包的一些信息,包括时间戳,rtp数据buff,发送的socket相关信息
* stream_type[in] 数据类型 0 视频 1 音频
*@return: 0 success others failed
*/
int gb28181_streampackageForH264(char *pData, int nFrameLen, Data_Info_s* pPacker, int stream_type)
{
char szTempPacketHead[256];
int nSizePos = 0;
int nSize = 0;
char *pBuff = NULL;
memset(szTempPacketHead, 0, 256);
// 1 package for ps header
gb28181_make_ps_header(szTempPacketHead + nSizePos, pPacker->s64CurPts);
nSizePos += PS_HDR_LEN;
//2 system header
if( pPacker->IFrame == 1 )
{
// 如果是I帧的话,则添加系统头
gb28181_make_sys_header(szTempPacketHead + nSizePos);
nSizePos += SYS_HDR_LEN;
//这个地方我是不管是I帧还是p帧都加上了map的,貌似只是I帧加也没有问题
// gb28181_make_psm_header(szTempPacketHead + nSizePos);
// nSizePos += PSM_HDR_LEN;
}
// psm头 (也是map)
gb28181_make_psm_header(szTempPacketHead + nSizePos);
nSizePos += PSM_HDR_LEN;
//加上rtp发送出去,这样的话,后面的数据就只要分片分包就只有加上pes头和rtp头了
if(gb28181_send_rtp_pack(szTempPacketHead, nSizePos, 0, pPacker) != 0 )
return -1;
// 这里向后移动是为了方便拷贝pes头
//这里是为了减少后面音视频裸数据的大量拷贝浪费空间,所以这里就向后移动,在实际处理的时候,要注意地址是否越界以及覆盖等问题
pBuff = pData - PES_HDR_LEN;
while(nFrameLen > 0)
{
//每次帧的长度不要超过short类型,过了就得分片进循环行发送
nSize = (nFrameLen > PS_PES_PAYLOAD_SIZE) ? PS_PES_PAYLOAD_SIZE : nFrameLen;
// 添加pes头
gb28181_make_pes_header(pBuff, stream_type ? 0xC0:0xE0, nSize, (pPacker->s64CurPts / 100), (pPacker->s64CurPts/300));
//最后在添加rtp头并发送数据
if( gb28181_send_rtp_pack(pBuff, nSize + PES_HDR_LEN, ((nSize == nFrameLen)?1:0), pPacker) != 0 )
{
printf("gb28181_send_pack failed!\n");
return -1;
}
//分片后每次发送的数据移动指针操作
nFrameLen -= nSize;
//这里也只移动nSize,因为在while向后移动的pes头长度,正好重新填充pes头数据
pBuff += nSize;
}
return 0;
}
上面列出来了整个打包发包的过程,接下来一个一个接口的看
/***
*@remark: ps头的封装,里面的具体数据的填写已经占位,可以参考标准
*@param : pData [in] 填充ps头数据的地址
* s64Src [in] 时间戳
*@return: 0 success, others failed
*/
int gb28181_make_ps_header(char *pData, unsigned long long s64Scr)
{