gb28181 ps流文件解析
Posted qianbo_insist
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了gb28181 ps流文件解析相关的知识,希望对你有一定的参考价值。
基础
假定是udp上接收包,端口 5060 ,如果是tcp是要加一些额外得代码,
可以使用jrtplib,或者自己写udpserver 放出udp socket 接收包,当然使用jrtplib等稍微简单一些。不过也一样失去了一些灵活性。
步骤
1 接收udp over rtp包
2 接收后分析ps包头,获取es流
3 如果不是ps则接收到包后根据rtp协议再解析rtp, 获取分片等信息组合成包
4 分析es流
code
#define FILE_STORAGE 0
Service_RTP_Analyse::Service_RTP_Analyse()
Service_RTP_Analyse::~Service_RTP_Analyse()
int Service_RTP_Analyse::analyse_rtp(uint8_t * rtp, size_t len)
return 0;
//注意这里只是适应h264
int Service_RTP_Analyse::analyse_h264es(uint8_t * pos, int len, uint8_t ** nalu, size_t &nalulen)
uint8_t* p = pos + 4 + 2 + 2; //跳过4字节00 00 01 E0 和两个字节长度 以及两个字节的跳空
int skip = *p;
p = p + skip + 1; //p 指向 00 00 00 01 67 等字节上
*nalu = p;
while (!*(p++));
uint8_t c1 = (uint8_t)(*p);
unsigned char nal_type = c1 & 0x1f;
nalulen = len - (int)(*nalu - pos); //因为 00 00 01 E0 以及两个代表长度的字节没有计算
return nal_type;
int Service_RTP_Analyse::analyseps0(uint8_t * ps, size_t len, int &type, int &length, uint8_t **next, size_t &nextlen)
#define D(y,x) (*(y+x))
if (D(ps, 0) == 0x00 && D(ps, 1) == 0x00 && D(ps, 2) == 0x01)
uint8_t num = D(ps, 3);
switch (num)
case 0xBA:
int skip1 = D(ps, 13) & 0x07;
uint8_t * nps = ps + 14 + skip1;
length = 14;
type = PS_PS;
*next = nps;
nextlen = len - (nps - ps);
return 0;
case 0xBB://系统标题起始码字段
case 0xBC://映射流标识字段
char temp[2];
temp[0] = D(ps, 5);
temp[1] = D(ps, 4);
length = *(unsigned short*)(&temp[0]) + 6; //00 00 01 xx 以及两个字节的长度
type = PS_SYS;
*next = ps + length;
nextlen = len - (int)(*next - ps);
return 0;
case 0xE0:
char temp[2];
temp[0] = D(ps, 5);
temp[1] = D(ps, 4);
length = *(unsigned short*)(&temp[0]) + 6;//00 00 01 xx 以及两个字节的长度
type = PS_VIDEO;
*next = ps + length;
nextlen = len -(int)(*next - ps);
return 0;
case 0xBD:
case 0xC0:
char temp[2];
temp[0] = D(ps, 5);
temp[1] = D(ps, 4);
length = *(unsigned short*)(&temp[0]) + 6;//00 00 01 xx 以及两个字节的长度
if (num == 0xBD)
type = PS_PRIVATE;
else
type = PS_AUDIO;
*next = ps + length;
nextlen = len - (int)(*next - ps);
return 0;
default:
length = 0;
type = PS_UNKONWN;
return -1;
else
length = 0;
type = PS_UNKONWN;
return -1;
/*
//该函数解一帧完整的ps,输入时一帧完整的ps帧
//rc 上下文环境
//pos ps流数据指针
//len 长度
*/
int Service_RTP_Analyse::analyseps(rtp_context *prc, uint8_t * pos, size_t len)
int type = 0;
int length = 0; //当前段长度
uint8_t * ps = pos;
size_t pslen = len;
uint8_t * nextpos = NULL;
size_t nextlen = 0;
uint8_t * sps = NULL;
int spslen = 0;
uint8_t * pps = NULL;
int ppslen = 0;
uint8_t * se = NULL;
int selen = 0;
uint8_t *nalu = NULL;
size_t nalulen = 0;
while (1)
int ret = analyseps0(ps, pslen, type, length, &nextpos, nextlen);
if (ret == -1)
return -1;
switch (type) //处理当前段
case PS_PS:
ps = nextpos;
pslen = nextlen;
break;
case PS_SYS:
ps = nextpos;
pslen = nextlen;
break;
case PS_VIDEO:
int type = analyse_h264es(ps, length, &nalu, nalulen);
switch (type)
case 0x07:
sps = nalu;
spslen = (int)nalulen;
break;
case 0x08:
pps = nalu;
ppslen = (int)nalulen;
break;
case 0x06: //skip
se = nalu;
selen = (int)nalulen;
break;
case 0x05:
case 0x01://skip,because except sps pps ,only one 0x05 key frame nalu
break;
default:
break;
ps = nextpos;
pslen = nextlen;
break;
case PS_PRIVATE:
ps = nextpos;
pslen = nextlen;
break;
case PS_AUDIO:
ps = nextpos;
pslen = nextlen;
break;
if (nextlen <= 0)
break;
//顺序找到了流以后,开始移动
//有关键帧,则移动sps和pps
#if FILE_STORAGE
if (ar->fp == NULL)
ar->fp = fopen("h264save.264", "wb");
if (ar->fpps == NULL)
ar->fpps = fopen("h264saveps.264", "wb");
fwrite(pos, 1, len, ar->fpps);
#endif
if (sps != NULL && pps != NULL)
//获取完整的一帧
//移动少量数据,达到意义上的零拷贝
memmove(nalu - ppslen, pps, ppslen);
memmove(nalu - ppslen - spslen, sps, spslen);
sps = nalu - ppslen - spslen;
pps = nalu - ppslen;
if (prc->_callback != NULL)
prc->_callback(prc->ip,
prc->port,
sps,
ppslen + spslen + nalulen,
KEY_FRAME,
sps,
spslen + ppslen,
prc->pts++);
#if FILE_STORAGE
fwrite(sps, 1, spslen + ppslen + nalulen, ar->fp);
#endif
else //非关键帧
if (prc->_callback != NULL)
prc->_callback(prc->ip, prc->port, nalu, nalulen, NOT_KEY_FRAME,NULL,0, prc->pts++);
#if FILE_STORAGE
fwrite(nalu, 1, nalulen, ar->fp);
#endif
return 0;
//ar 因为零拷贝的事情buffer可能没有值
int Service_RTP_Analyse::insert(uint32_t ip, uint16_t port, void *pkt0)
RTPPacket * pkt = (RTPPacket*)pkt0;
//if (pkt->GetPayloadLength() > BUF_LENGTH) //这里还是要修改 钱波
// return -1;
auto iter = _hash.find(ip);
if (iter == _hash.end()) //没有找到该IP地址的存储
OutputDebugString(L"not found");
uint8_t * ps = pkt->GetPayloadData();
//分析ps流
if (*ps == 0x00 && *(ps + 1) == 0x00 && *(ps + 2) == 0x01 && *(ps + 3) == 0xBA)
analyse_struct as;
as.in = pkt->GetPayloadData();
as.inlen = pkt->GetPayloadLength();
AnalyseH264Frame(&as);
if (as.ret == 0) //非关键帧
OutputDebugString(L"第一RTP包非关键帧头部,丢弃\\n");
return -1;
rtp_context rc;
rc.ip = ip;
rc.port = port;
rc._callback = _callback;
if (pkt->HasMarker())
rc.blen = 0;
analyseps(&rc, pkt->GetPayloadData(), pkt->GetPayloadLength());
//send_func(&rc, ip, port, pkt->GetPayloadData(), pkt->GetPayloadLength(), 0);
else
ALLOC_MEM(rc);
rc.blen= pkt->GetPayloadLength();
memcpy(rc.buffer, pkt->GetPayloadData(), rc.blen);
//fixme:qianbo
//_hash.emplace(pair<uint32_t, rtp_context>(ip, as0));
_hash.insert(pair<uint32_t, rtp_context>(ip, rc));
else //分析普通rtp流
else //找到存储
//代码优化零拷贝
rtp_context &rc = iter->second;
size_t &len = rc.blen;
if (pkt->HasMarker()) // 回调启用
if (len == 0)
//执行零拷贝操作
analyseps(&rc, pkt->GetPayloadData(), pkt->GetPayloadLength());
//send_func(&rc, ip, port, pkt->GetPayloadData(), pkt->GetPayloadLength(), 0);
else // len!=0
uint8_t * buf = iter->second.buffer;
memcpy(buf + len, pkt->GetPayloadData(), pkt->GetPayloadLength());
len += pkt->GetPayloadLength();
analyseps(&rc, buf, len);
//send_func(&rc, ip, port, buf, len, 0);
len = 0;
else
ALLOC_MEM(rc);
uint8_t * buf = iter->second.buffer;
memcpy(buf + len, pkt->GetPayloadData(), pkt->GetPayloadLength());
len += pkt->GetPayloadLength();
return 0;
// 功能:解码RTP H.264视频
// 参数:1.RTP包缓冲地址 2.RTP包数据大小 3.H264输出地址 4.输出数据大小
// 返回:true:表示一帧结束 false:FU-A分片未结束或帧未结束
bool Service_RTP_Analyse::decode_rtp_h264(void *bufIn, int len, void **pBufOut, int *pOutLen)
*pOutLen = 0;
if (len < RTP_HEADLEN)
return false;
uint8_t *src = (uint8_t*)bufIn + RTP_HEADLEN;
//第1字节
uint8_t head_1 = *src;
//第2字节
uint8_t head_2 = *(src + 1);
//FU indicator的类型域,
uint8_t nal = head_1 & 0x1f;
//FU header的前三位,当前是分包的1开始 2中间 3结束
uint8_t flag = head_2 & 0xe0;
uint8_t nal_fua = (head_1 & 0xe0) | (head_2 & 0x1f); // FU_A nal
bool bFinishFrame = false;
if (nal == 0x1c) // 判断NAL的类型为0x1c=28,说明是FU-A分片
// fu-a
if (flag == 0x80) // 开始
*pBufOut = src - 3;
*((int *)(*pBufOut)) = 0x01000000; //
*((char *)(*pBufOut) + 4) = nal_fua;
*pOutLen = len - RTP_HEADLEN + 3;
else if (flag == 0x40) // 结束
*pBufOut = src + 2;
*pOutLen = len - RTP_HEADLEN - 2;
else // 中间
*pBufOut = src + 2;
*pOutLen = len - RTP_HEADLEN - 2;
else // 单包数据
*pBufOut = src - 4;
*((int *)(*pBufOut)) = 0x01000000; // 大模式会有问题
*pOutLen = len - RTP_HEADLEN + 4;
uint8_t * tmp = (unsigned char*)bufIn;
bFinishFrame = (tmp[1] & 0x80) ? true : false;
return bFinishFrame;
//功能:解RTP AAC音频包,声道和采样频率必须知道。
//参数:1.RTP包缓冲地址 2.RTP包数据大小 3.H264输出地址 4.输出数据大小
//返回:true:表示一帧结束 false:帧未结束 一般AAC音频包比较小,没有分片。
bool Service_RTP_Analyse::decode_rtp_aac(void * bufIn, int recvLen, void** pBufOut, int* pOutLen)
unsigned char* bufRecv = (unsigned char*)bufIn;
//char strFileName[20];
unsigned char ADTS[] = 0xFF, 0xF1以上是关于gb28181 ps流文件解析的主要内容,如果未能解决你的问题,请参考以下文章