gb28181协议流媒体实现为rtp荷载ps流,将h264流打包成ps流。
RTP荷载PS流
针对H264 做如下PS 封装:每个IDR NALU 前一般都会包含SPS、PPS 等NALU,因此将SPS、PPS、IDR 的NALU 封装为一个PS 包,包括ps 头,然后加上PS system header,PS system map,PES header+h264 raw data。所以一个IDR NALU PS 包由外到内顺序是:PSheader| PS system header | PS system Map | PES header | h264 raw data。对于其它非关键帧的PS 包,就简单多了,直接加上PS头和PES 头就可以了。顺序为:PS header | PES header | h264raw data。以上是对只有视频video 的情况,如果要把音频Audio也打包进PS 封装,也可以。当有音频数据时,将数据加上PES header 放到视频PES 后就可以了。顺序如下:PS 包=PS头|PES(video)|PES(audio),再用RTP 封装发送就可以了。
PS 包中的流类型(stream type)的取值如下:
音视频流格式 | 取值 |
---|---|
MPEG-4 视频流 | 0x10 |
MPEG-4 视频流 | 0x10 |
MPEG-4 视频流 | 0x10 |
H.264 视频流 | 0x1B |
SVAC 视频流 | 0x80 |
G.711 音频流 | 0x90 |
G.722.1 音频流 | 0x92 |
G.723.1 音频流 | 0x93 |
G.729 音频流 | 0x99 |
SVAC音频流 | 0x9B |
H264封装成PS
封装注意点:
当我们从读缓冲区中取得一帧音视频数据的时候,封装时其实每一帧数据有且只有一个ps头和psm头,如果是I帧的话,就还多一个system头,一个或者多个pes头和rtp头,像如果帧数据过长的话,就得进行分片,每片都会包含一个pes头,rtp负载最好长度1460,所以会进行再分包操作!所以每一个包数据至少一个rtp+databuf,每一片数据,至少有个rtp+pes+databuf,每一帧数据至少有rtp+ps+psm+pes+databuf(关键帧的话:多一个system头)
各个封装模块的代码实现:
/***
*@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 headergb28181_make_ps_header(szTempPacketHead + nSizePos, pPacker->s64CurPts);nSizePos += PS_HDR_LEN;//2 system headerif (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, pPacker->s64CurPts);//最后在添加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;
}
ps头的封装:
/***
*@remark: ps头的封装,里面的具体数据的填写已经占位,可以参考标准
*@param : pData [in] 填充ps头数据的地址
* s64Src [in] 时间戳
*@return: 0 success, others failed
*/
int gb28181_make_ps_header(char *pData, unsigned long long s64Scr)
{unsigned long long lScrExt = (s64Scr) % 100;//s64Scr = s64Scr / 100;// 这里除以100是由于sdp协议返回的video的频率是90000,帧率是25帧/s,所以每次递增的量是3600,// 所以实际你应该根据你自己编码里的时间戳来处理以保证时间戳的增量为3600即可,//如果这里不对的话,就可能导致卡顿现象了bits_buffer_s bitsBuffer;bitsBuffer.i_size = PS_HDR_LEN;bitsBuffer.i_data = 0;bitsBuffer.i_mask = 0x80; // 二进制:10000000 这里是为了后面对一个字节的每一位进行操作,避免大小端夸字节字序错乱bitsBuffer.p_data = (unsigned char *)(pData);memset(bitsBuffer.p_data, 0, PS_HDR_LEN);bits_write(&bitsBuffer, 32, 0x000001BA); /*start codes*/bits_write(&bitsBuffer, 2, 1); /*marker bits '01b'*/bits_write(&bitsBuffer, 3, (s64Scr >> 30) & 0x07); /*System clock [32..30]*/bits_write(&bitsBuffer, 1, 1); /*marker bit*/bits_write(&bitsBuffer, 15, (s64Scr >> 15) & 0x7FFF); /*System clock [29..15]*/bits_write(&bitsBuffer, 1, 1); /*marker bit*/bits_write(&bitsBuffer, 15, s64Scr & 0x7fff); /*System clock [14..0]*/bits_write(&bitsBuffer, 1, 1); /*marker bit*/bits_write(&bitsBuffer, 9, lScrExt & 0x01ff); /*System clock ext*/bits_write(&bitsBuffer, 1, 1); /*marker bit*/bits_write(&bitsBuffer, 22, (255) & 0x3fffff); /*bit rate(n units of 50 bytes per second.)*/bits_write(&bitsBuffer, 2, 3); /*marker bits '11'*/bits_write(&bitsBuffer, 5, 0x1f); /*reserved(reserved for future use)*/bits_write(&bitsBuffer, 3, 0); /*stuffing length*/return 0;
}
sys头的封装:
/***
*@remark: sys头的封装,里面的具体数据的填写已经占位,可以参考标准
*@param : pData [in] 填充ps头数据的地址
*@return: 0 success, others failed
*/
int gb28181_make_sys_header(char *pData)
{bits_buffer_s bitsBuffer;bitsBuffer.i_size = SYS_HDR_LEN;bitsBuffer.i_data = 0;bitsBuffer.i_mask = 0x80;bitsBuffer.p_data = (unsigned char *)(pData);memset(bitsBuffer.p_data, 0, SYS_HDR_LEN);/*system header*/bits_write(&bitsBuffer, 32, 0x000001BB); /*start code*/bits_write(&bitsBuffer, 16, SYS_HDR_LEN - 6);/*header_length 表示次字节后面的长度,后面的相关头也是次意思*/bits_write(&bitsBuffer, 1, 1); /*marker_bit*/bits_write(&bitsBuffer, 22, 50000); /*rate_bound*/bits_write(&bitsBuffer, 1, 1); /*marker_bit*/bits_write(&bitsBuffer, 6, 1); /*audio_bound*/bits_write(&bitsBuffer, 1, 0); /*fixed_flag */bits_write(&bitsBuffer, 1, 1); /*CSPS_flag */bits_write(&bitsBuffer, 1, 1); /*system_audio_lock_flag*/bits_write(&bitsBuffer, 1, 1); /*system_video_lock_flag*/bits_write(&bitsBuffer, 1, 1); /*marker_bit*/bits_write(&bitsBuffer, 5, 1); /*video_bound*/bits_write(&bitsBuffer, 1, 0); /*dif from mpeg1*/bits_write(&bitsBuffer, 7, 0x7F); /*reserver*//*audio stream bound*/bits_write(&bitsBuffer, 8, 0xC0); /*stream_id*/bits_write(&bitsBuffer, 2, 3); /*marker_bit */bits_write(&bitsBuffer, 1, 0); /*PSTD_buffer_bound_scale*/bits_write(&bitsBuffer, 13, 512); /*PSTD_buffer_size_bound*//*video stream bound*/bits_write(&bitsBuffer, 8, 0xE0); /*stream_id*/bits_write(&bitsBuffer, 2, 3); /*marker_bit */bits_write(&bitsBuffer, 1, 1); /*PSTD_buffer_bound_scale*/bits_write(&bitsBuffer, 13, 2048); /*PSTD_buffer_size_bound*/return 0;
}
psm头的封装:
/***
*@remark: psm头的封装,里面的具体数据的填写已经占位,可以参考标准
*@param : pData [in] 填充ps头数据的地址
*@return: 0 success, others failed
*/
int gb28181_make_psm_header(char *pData)
{bits_buffer_s bitsBuffer;bitsBuffer.i_size = PSM_HDR_LEN;bitsBuffer.i_data = 0;bitsBuffer.i_mask = 0x80;bitsBuffer.p_data = (unsigned char *)(pData);memset(bitsBuffer.p_data, 0, PSM_HDR_LEN);bits_write(&bitsBuffer, 24, 0x000001); /*start code*/bits_write(&bitsBuffer, 8, 0xBC); /*map stream id*/bits_write(&bitsBuffer, 16, 18); /*program stream map length*/bits_write(&bitsBuffer, 1, 1); /*current next indicator */bits_write(&bitsBuffer, 2, 3); /*reserved*/bits_write(&bitsBuffer, 5, 0); /*program stream map version*/bits_write(&bitsBuffer, 7, 0x7F); /*reserved */bits_write(&bitsBuffer, 1, 1); /*marker bit */bits_write(&bitsBuffer, 16, 0); /*programe stream info length*/bits_write(&bitsBuffer, 16, 8); /*elementary stream map length is*//*audio*/bits_write(&bitsBuffer, 8, 0x90); /*stream_type*/bits_write(&bitsBuffer, 8, 0xC0); /*elementary_stream_id*/bits_write(&bitsBuffer, 16, 0); /*elementary_stream_info_length is*//*video*/bits_write(&bitsBuffer, 8, 0x1B); /*stream_type*/bits_write(&bitsBuffer, 8, 0xE0); /*elementary_stream_id*/bits_write(&bitsBuffer, 16, 0); /*elementary_stream_info_length *//*crc (2e b9 0f 3d)*/bits_write(&bitsBuffer, 8, 0x45); /*crc (24~31) bits*/bits_write(&bitsBuffer, 8, 0xBD); /*crc (16~23) bits*/bits_write(&bitsBuffer, 8, 0xDC); /*crc (8~15) bits*/bits_write(&bitsBuffer, 8, 0xF4); /*crc (0~7) bits*/return 0;
}
pes头的封装:
/***
*@remark: pes头的封装,里面的具体数据的填写已经占位,可以参考标准
*@param : pData [in] 填充ps头数据的地址
* stream_id [in] 码流类型
* paylaod_len[in] 负载长度
* pts [in] 时间戳
* dts [in]
*@return: 0 success, others failed
*/
int gb28181_make_pes_header(char *pData, int stream_id, int payload_len, unsigned long long pts, unsigned long long dts)
{bits_buffer_s bitsBuffer;bitsBuffer.i_size = PES_HDR_LEN;bitsBuffer.i_data = 0;bitsBuffer.i_mask = 0x80;bitsBuffer.p_data = (unsigned char *)(pData);memset(bitsBuffer.p_data, 0, PES_HDR_LEN);/*system header*/bits_write(&bitsBuffer, 24, 0x000001); /*start code*/bits_write(&bitsBuffer, 8, (stream_id)); /*streamID*/bits_write(&bitsBuffer, 16, (payload_len)+13); /*packet_len*/ //指出pes分组中数据长度和该字节后的长度和bits_write(&bitsBuffer, 2, 2); /*'10'*/bits_write(&bitsBuffer, 2, 0); /*scrambling_control*/bits_write(&bitsBuffer, 1, 0); /*priority*/bits_write(&bitsBuffer, 1, 0); /*data_alignment_indicator*/bits_write(&bitsBuffer, 1, 0); /*copyright*/bits_write(&bitsBuffer, 1, 0); /*original_or_copy*/bits_write(&bitsBuffer, 1, 1); /*PTS_flag*/bits_write(&bitsBuffer, 1, 1); /*DTS_flag*/bits_write(&bitsBuffer, 1, 0); /*ESCR_flag*/bits_write(&bitsBuffer, 1, 0); /*ES_rate_flag*/bits_write(&bitsBuffer, 1, 0); /*DSM_trick_mode_flag*/bits_write(&bitsBuffer, 1, 0); /*additional_copy_info_flag*/bits_write(&bitsBuffer, 1, 0); /*PES_CRC_flag*/bits_write(&bitsBuffer, 1, 0); /*PES_extension_flag*/bits_write(&bitsBuffer, 8, 10); /*header_data_length*/// 指出包含在 PES 分组标题中的可选字段和任何填充字节所占用的总字节数。该字段之前//的字节指出了有无可选字段。/*PTS,DTS*/bits_write(&bitsBuffer, 4, 3); /*'0011'*/bits_write(&bitsBuffer, 3, ((pts) >> 30) & 0x07); /*PTS[32..30]*/bits_write(&bitsBuffer, 1, 1);bits_write(&bitsBuffer, 15, ((pts) >> 15) & 0x7FFF); /*PTS[29..15]*/bits_write(&bitsBuffer, 1, 1);bits_write(&bitsBuffer, 15, (pts)& 0x7FFF); /*PTS[14..0]*/bits_write(&bitsBuffer, 1, 1);bits_write(&bitsBuffer, 4, 1); /*'0001'*/bits_write(&bitsBuffer, 3, ((dts) >> 30) & 0x07); /*DTS[32..30]*/bits_write(&bitsBuffer, 1, 1);bits_write(&bitsBuffer, 15, ((dts) >> 15) & 0x7FFF); /*DTS[29..15]*/bits_write(&bitsBuffer, 1, 1);bits_write(&bitsBuffer, 15, (dts)& 0x7FFF); /*DTS[14..0]*/bits_write(&bitsBuffer, 1, 1);return 0;
}
rtp头的打包,并循环发送数据:
/***
*@remark: rtp头的打包,并循环发送数据
*@param : pData [in] 发送的数据地址
* nDatalen [in] 发送数据的长度
* mark_flag [in] mark标志位
* curpts [in] 时间戳
* pPacker [in] 数据包的基本信息
*@return: 0 success, others failed
*/int gb28181_send_rtp_pack(char *databuff, int nDataLen, int mark_flag, Data_Info_s* pPacker)
{int nRes = 0;int nPlayLoadLen = 0;int nSendSize = 0;char szRtpHdr[RTP_HDR_LEN];memset(szRtpHdr, 0, RTP_HDR_LEN);if (nDataLen + RTP_HDR_LEN <= RTP_MAX_PACKET_BUFF)// 1460 pPacker指针本来有一个1460大小的buffer数据缓存{// 一帧数据发送完后,给mark标志位置1gb28181_make_rtp_header(szRtpHdr, ((mark_flag == 1) ? 1 : 0), ++pPacker->u16CSeq, pPacker->s64CurPts, pPacker->u32Ssrc);memcpy(pPacker->szBuff, szRtpHdr, RTP_HDR_LEN);memcpy(pPacker->szBuff + RTP_HDR_LEN, databuff, nDataLen);nRes = SendDataBuff(pPacker->szBuff, nDataLen + RTP_HDR_LEN);if (nRes != (RTP_HDR_LEN + nDataLen)){printf(" udp send error !\n");return -1;}}else{nPlayLoadLen = RTP_MAX_PACKET_BUFF - RTP_HDR_LEN; // 每次只能发送的数据长度 除去rtp头gb28181_make_rtp_header(pPacker->szBuff, 0, ++pPacker->u16CSeq, pPacker->s64CurPts, pPacker->u32Ssrc);memcpy(pPacker->szBuff + RTP_HDR_LEN, databuff, nPlayLoadLen);nRes = SendDataBuff(pPacker->szBuff, RTP_HDR_LEN + nPlayLoadLen);if (nRes != (RTP_HDR_LEN + nPlayLoadLen)){printf(" udp send error !\n");return -1;}nDataLen -= nPlayLoadLen;// databuff += (nPlayLoadLen - RTP_HDR_LEN);databuff += nPlayLoadLen; // 表明前面到数据已经发送出去databuff -= RTP_HDR_LEN; // 用来存放rtp头while (nDataLen > 0){if (nDataLen <= nPlayLoadLen){//一帧数据发送完,置mark标志位gb28181_make_rtp_header(databuff, mark_flag, ++pPacker->u16CSeq, pPacker->s64CurPts, pPacker->u32Ssrc);nSendSize = nDataLen;}else{gb28181_make_rtp_header(databuff, 0, ++pPacker->u16CSeq, pPacker->s64CurPts, pPacker->u32Ssrc);nSendSize = nPlayLoadLen;}nRes = SendDataBuff(databuff, RTP_HDR_LEN + nSendSize);if (nRes != (RTP_HDR_LEN + nSendSize)){printf(" udp send error !\n");return -1;}nDataLen -= nSendSize;databuff += nSendSize;//因为buffer指针已经向后移动一次rtp头长度后,//所以每次循环发送rtp包时,只要向前移动裸数据到长度即可,这是buffer指针实际指向到位置是//databuff向后重复的rtp长度的裸数据到位置上}}return 0;
}
rtp封装头:
int gb28181_make_rtp_header(char *pData, int marker_flag, unsigned short cseq, long long curpts, unsigned int ssrc)
{bits_buffer_s bitsBuffer;if (pData == NULL)return -1;bitsBuffer.i_size = RTP_HDR_LEN;bitsBuffer.i_data = 0;bitsBuffer.i_mask = 0x80;bitsBuffer.p_data = (unsigned char *)(pData);memset(bitsBuffer.p_data, 0, RTP_HDR_LEN);bits_write(&bitsBuffer, 2, RTP_VERSION); /* rtp version */bits_write(&bitsBuffer, 1, 0); /* rtp padding */bits_write(&bitsBuffer, 1, 0); /* rtp extension */bits_write(&bitsBuffer, 4, 0); /* rtp CSRC count */bits_write(&bitsBuffer, 1, (marker_flag)); /* rtp marker */bits_write(&bitsBuffer, 7, 96); /* rtp payload type*/bits_write(&bitsBuffer, 16, (cseq)); /* rtp sequence */bits_write(&bitsBuffer, 32, (curpts)); /* rtp timestamp */bits_write(&bitsBuffer, 32, (ssrc)); /* rtp SSRC */return 0;
}
编译:
gcc main.cpp -g -o main
vlc播放:
首先,要编写一个sdp文件(1.sdp),供vlc进行播放(播放方式:打开vlc->媒体->打开多个文件->添加1.sdp->播放)。
m=video 20002 RTP/AVP 96
a=rtpmap:96 MP2P/90000
a=framerate:25
c=IN IP4 192.168.30.166
贴上完整代码:
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <syslog.h>
#include <dirent.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <sys/ioctl.h>#define PS_HDR_LEN 14
#define SYS_HDR_LEN 18
#define PSM_HDR_LEN 24
#define PES_HDR_LEN 19
#define RTP_HDR_LEN 12
#define RTP_VERSION 2
#define RTP_MAX_PACKET_BUFF 1460
#define PS_PES_PAYLOAD_SIZE 65522union LESize
{unsigned short int length;unsigned char byte[2];
};struct bits_buffer_s {unsigned char* p_data;unsigned char i_mask;int i_size;int i_data;
};struct Data_Info_s {uint64_t s64CurPts;int IFrame;uint16_t u16CSeq;uint32_t u32Ssrc;char szBuff[RTP_MAX_PACKET_BUFF];
};int _socketFd;/***
*@remark: 讲传入的数据按地位一个一个的压入数据
*@param : buffer [in] 压入数据的buffer
* count [in] 需要压入数据占的位数
* bits [in] 压入的数值
*/
#define bits_write(buffer, count, bits)\
{\bits_buffer_s *p_buffer = (buffer); \int i_count = (count); \uint64_t i_bits = (bits); \
while (i_count > 0)\
{\i_count--; \
if ((i_bits >> i_count) & 0x01)\
{\p_buffer->p_data[p_buffer->i_data] |= p_buffer->i_mask; \
}\else\
{\p_buffer->p_data[p_buffer->i_data] &= ~p_buffer->i_mask; \
}\p_buffer->i_mask >>= 1;/*操作完一个字节第一位后,操作第二位*/\
if (p_buffer->i_mask == 0) /*循环完一个字节的8位后,重新开始下一位*/\
{\p_buffer->i_data++; \p_buffer->i_mask = 0x80; \
}\
}\
}int gb28181_make_rtp_header(char *pData, int marker_flag, unsigned short cseq, long long curpts, unsigned int ssrc);
int gb28181_send_rtp_pack(char *databuff, int nDataLen, int mark_flag, Data_Info_s* pPacker);
int gb28181_make_pes_header(char *pData, int stream_id, int payload_len, unsigned long long pts, unsigned long long dts);
int gb28181_make_psm_header(char *pData);
int gb28181_make_sys_header(char *pData);
int gb28181_make_ps_header(char *pData, unsigned long long s64Scr);
int SendDataBuff(char* buff, int size);int findStartCode(unsigned char *buf, int zeros_in_startcode)
{int info;int i;info = 1;for (i = 0; i < zeros_in_startcode; i++)if (buf[i] != 0)info = 0;if (buf[i] != 1)info = 0;return info;
}int getNextNalu(FILE* inpf, unsigned char* buf)
{int pos = 0;int startCodeFound = 0;int info2 = 0;int info3 = 0;while (!feof(inpf) && (buf[pos++] = fgetc(inpf)) == 0); // fgetc:读取成功时返回读取到的字符,读取到文件末尾或读取失败时返回EOFwhile (!startCodeFound){if (feof(inpf)) //feof:其功能是检测流上的文件结束符,如果文件结束,则返回非0值,否则返回0{return pos - 1;}buf[pos++] = fgetc(inpf);info3 = findStartCode(&buf[pos - 4], 3);startCodeFound = (info3 == 1);if (info3 != 1)info2 = findStartCode(&buf[pos - 3], 2);startCodeFound = (info2 == 1 || info3 == 1);}if (info2){fseek(inpf, -3, SEEK_CUR); //fseek:重定位流(数据流/文件)上的文件内部位置指针 返回值:成功,返回0,失败返回非0值,并设置error的值 SEEK_CUR 1return pos - 3;}if (info3){fseek(inpf, -4, SEEK_CUR);return pos - 4;}
}/***
*@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 headergb28181_make_ps_header(szTempPacketHead + nSizePos, pPacker->s64CurPts);nSizePos += PS_HDR_LEN;//2 system headerif (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, pPacker->s64CurPts);//最后在添加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)
{unsigned long long lScrExt = (s64Scr) % 100;//s64Scr = s64Scr / 100;// 这里除以100是由于sdp协议返回的video的频率是90000,帧率是25帧/s,所以每次递增的量是3600,// 所以实际你应该根据你自己编码里的时间戳来处理以保证时间戳的增量为3600即可,//如果这里不对的话,就可能导致卡顿现象了bits_buffer_s bitsBuffer;bitsBuffer.i_size = PS_HDR_LEN;bitsBuffer.i_data = 0;bitsBuffer.i_mask = 0x80; // 二进制:10000000 这里是为了后面对一个字节的每一位进行操作,避免大小端夸字节字序错乱bitsBuffer.p_data = (unsigned char *)(pData);memset(bitsBuffer.p_data, 0, PS_HDR_LEN);bits_write(&bitsBuffer, 32, 0x000001BA); /*start codes*/bits_write(&bitsBuffer, 2, 1); /*marker bits '01b'*/bits_write(&bitsBuffer, 3, (s64Scr >> 30) & 0x07); /*System clock [32..30]*/bits_write(&bitsBuffer, 1, 1); /*marker bit*/bits_write(&bitsBuffer, 15, (s64Scr >> 15) & 0x7FFF); /*System clock [29..15]*/bits_write(&bitsBuffer, 1, 1); /*marker bit*/bits_write(&bitsBuffer, 15, s64Scr & 0x7fff); /*System clock [14..0]*/bits_write(&bitsBuffer, 1, 1); /*marker bit*/bits_write(&bitsBuffer, 9, lScrExt & 0x01ff); /*System clock ext*/bits_write(&bitsBuffer, 1, 1); /*marker bit*/bits_write(&bitsBuffer, 22, (255) & 0x3fffff); /*bit rate(n units of 50 bytes per second.)*/bits_write(&bitsBuffer, 2, 3); /*marker bits '11'*/bits_write(&bitsBuffer, 5, 0x1f); /*reserved(reserved for future use)*/bits_write(&bitsBuffer, 3, 0); /*stuffing length*/return 0;
}/***
*@remark: sys头的封装,里面的具体数据的填写已经占位,可以参考标准
*@param : pData [in] 填充ps头数据的地址
*@return: 0 success, others failed
*/
int gb28181_make_sys_header(char *pData)
{bits_buffer_s bitsBuffer;bitsBuffer.i_size = SYS_HDR_LEN;bitsBuffer.i_data = 0;bitsBuffer.i_mask = 0x80;bitsBuffer.p_data = (unsigned char *)(pData);memset(bitsBuffer.p_data, 0, SYS_HDR_LEN);/*system header*/bits_write(&bitsBuffer, 32, 0x000001BB); /*start code*/bits_write(&bitsBuffer, 16, SYS_HDR_LEN - 6);/*header_length 表示次字节后面的长度,后面的相关头也是次意思*/bits_write(&bitsBuffer, 1, 1); /*marker_bit*/bits_write(&bitsBuffer, 22, 50000); /*rate_bound*/bits_write(&bitsBuffer, 1, 1); /*marker_bit*/bits_write(&bitsBuffer, 6, 1); /*audio_bound*/bits_write(&bitsBuffer, 1, 0); /*fixed_flag */bits_write(&bitsBuffer, 1, 1); /*CSPS_flag */bits_write(&bitsBuffer, 1, 1); /*system_audio_lock_flag*/bits_write(&bitsBuffer, 1, 1); /*system_video_lock_flag*/bits_write(&bitsBuffer, 1, 1); /*marker_bit*/bits_write(&bitsBuffer, 5, 1); /*video_bound*/bits_write(&bitsBuffer, 1, 0); /*dif from mpeg1*/bits_write(&bitsBuffer, 7, 0x7F); /*reserver*//*audio stream bound*/bits_write(&bitsBuffer, 8, 0xC0); /*stream_id*/bits_write(&bitsBuffer, 2, 3); /*marker_bit */bits_write(&bitsBuffer, 1, 0); /*PSTD_buffer_bound_scale*/bits_write(&bitsBuffer, 13, 512); /*PSTD_buffer_size_bound*//*video stream bound*/bits_write(&bitsBuffer, 8, 0xE0); /*stream_id*/bits_write(&bitsBuffer, 2, 3); /*marker_bit */bits_write(&bitsBuffer, 1, 1); /*PSTD_buffer_bound_scale*/bits_write(&bitsBuffer, 13, 2048); /*PSTD_buffer_size_bound*/return 0;
}/***
*@remark: psm头的封装,里面的具体数据的填写已经占位,可以参考标准
*@param : pData [in] 填充ps头数据的地址
*@return: 0 success, others failed
*/
int gb28181_make_psm_header(char *pData)
{bits_buffer_s bitsBuffer;bitsBuffer.i_size = PSM_HDR_LEN;bitsBuffer.i_data = 0;bitsBuffer.i_mask = 0x80;bitsBuffer.p_data = (unsigned char *)(pData);memset(bitsBuffer.p_data, 0, PSM_HDR_LEN);bits_write(&bitsBuffer, 24, 0x000001); /*start code*/bits_write(&bitsBuffer, 8, 0xBC); /*map stream id*/bits_write(&bitsBuffer, 16, 18); /*program stream map length*/bits_write(&bitsBuffer, 1, 1); /*current next indicator */bits_write(&bitsBuffer, 2, 3); /*reserved*/bits_write(&bitsBuffer, 5, 0); /*program stream map version*/bits_write(&bitsBuffer, 7, 0x7F); /*reserved */bits_write(&bitsBuffer, 1, 1); /*marker bit */bits_write(&bitsBuffer, 16, 0); /*programe stream info length*/bits_write(&bitsBuffer, 16, 8); /*elementary stream map length is*//*audio*/bits_write(&bitsBuffer, 8, 0x90); /*stream_type*/bits_write(&bitsBuffer, 8, 0xC0); /*elementary_stream_id*/bits_write(&bitsBuffer, 16, 0); /*elementary_stream_info_length is*//*video*/bits_write(&bitsBuffer, 8, 0x1B); /*stream_type*/bits_write(&bitsBuffer, 8, 0xE0); /*elementary_stream_id*/bits_write(&bitsBuffer, 16, 0); /*elementary_stream_info_length *//*crc (2e b9 0f 3d)*/bits_write(&bitsBuffer, 8, 0x45); /*crc (24~31) bits*/bits_write(&bitsBuffer, 8, 0xBD); /*crc (16~23) bits*/bits_write(&bitsBuffer, 8, 0xDC); /*crc (8~15) bits*/bits_write(&bitsBuffer, 8, 0xF4); /*crc (0~7) bits*/return 0;
}/***
*@remark: pes头的封装,里面的具体数据的填写已经占位,可以参考标准
*@param : pData [in] 填充ps头数据的地址
* stream_id [in] 码流类型
* paylaod_len[in] 负载长度
* pts [in] 时间戳
* dts [in]
*@return: 0 success, others failed
*/
int gb28181_make_pes_header(char *pData, int stream_id, int payload_len, unsigned long long pts, unsigned long long dts)
{bits_buffer_s bitsBuffer;bitsBuffer.i_size = PES_HDR_LEN;bitsBuffer.i_data = 0;bitsBuffer.i_mask = 0x80;bitsBuffer.p_data = (unsigned char *)(pData);memset(bitsBuffer.p_data, 0, PES_HDR_LEN);/*system header*/bits_write(&bitsBuffer, 24, 0x000001); /*start code*/bits_write(&bitsBuffer, 8, (stream_id)); /*streamID*/bits_write(&bitsBuffer, 16, (payload_len)+13); /*packet_len*/ //指出pes分组中数据长度和该字节后的长度和bits_write(&bitsBuffer, 2, 2); /*'10'*/bits_write(&bitsBuffer, 2, 0); /*scrambling_control*/bits_write(&bitsBuffer, 1, 0); /*priority*/bits_write(&bitsBuffer, 1, 0); /*data_alignment_indicator*/bits_write(&bitsBuffer, 1, 0); /*copyright*/bits_write(&bitsBuffer, 1, 0); /*original_or_copy*/bits_write(&bitsBuffer, 1, 1); /*PTS_flag*/bits_write(&bitsBuffer, 1, 1); /*DTS_flag*/bits_write(&bitsBuffer, 1, 0); /*ESCR_flag*/bits_write(&bitsBuffer, 1, 0); /*ES_rate_flag*/bits_write(&bitsBuffer, 1, 0); /*DSM_trick_mode_flag*/bits_write(&bitsBuffer, 1, 0); /*additional_copy_info_flag*/bits_write(&bitsBuffer, 1, 0); /*PES_CRC_flag*/bits_write(&bitsBuffer, 1, 0); /*PES_extension_flag*/bits_write(&bitsBuffer, 8, 10); /*header_data_length*/// 指出包含在 PES 分组标题中的可选字段和任何填充字节所占用的总字节数。该字段之前//的字节指出了有无可选字段。/*PTS,DTS*/bits_write(&bitsBuffer, 4, 3); /*'0011'*/bits_write(&bitsBuffer, 3, ((pts) >> 30) & 0x07); /*PTS[32..30]*/bits_write(&bitsBuffer, 1, 1);bits_write(&bitsBuffer, 15, ((pts) >> 15) & 0x7FFF); /*PTS[29..15]*/bits_write(&bitsBuffer, 1, 1);bits_write(&bitsBuffer, 15, (pts)& 0x7FFF); /*PTS[14..0]*/bits_write(&bitsBuffer, 1, 1);bits_write(&bitsBuffer, 4, 1); /*'0001'*/bits_write(&bitsBuffer, 3, ((dts) >> 30) & 0x07); /*DTS[32..30]*/bits_write(&bitsBuffer, 1, 1);bits_write(&bitsBuffer, 15, ((dts) >> 15) & 0x7FFF); /*DTS[29..15]*/bits_write(&bitsBuffer, 1, 1);bits_write(&bitsBuffer, 15, (dts)& 0x7FFF); /*DTS[14..0]*/bits_write(&bitsBuffer, 1, 1);return 0;
}/***
*@remark: rtp头的打包,并循环发送数据
*@param : pData [in] 发送的数据地址
* nDatalen [in] 发送数据的长度
* mark_flag [in] mark标志位
* curpts [in] 时间戳
* pPacker [in] 数据包的基本信息
*@return: 0 success, others failed
*/int gb28181_send_rtp_pack(char *databuff, int nDataLen, int mark_flag, Data_Info_s* pPacker)
{int nRes = 0;int nPlayLoadLen = 0;int nSendSize = 0;char szRtpHdr[RTP_HDR_LEN];memset(szRtpHdr, 0, RTP_HDR_LEN);if (nDataLen + RTP_HDR_LEN <= RTP_MAX_PACKET_BUFF)// 1460 pPacker指针本来有一个1460大小的buffer数据缓存{// 一帧数据发送完后,给mark标志位置1gb28181_make_rtp_header(szRtpHdr, ((mark_flag == 1) ? 1 : 0), ++pPacker->u16CSeq, pPacker->s64CurPts, pPacker->u32Ssrc);memcpy(pPacker->szBuff, szRtpHdr, RTP_HDR_LEN);memcpy(pPacker->szBuff + RTP_HDR_LEN, databuff, nDataLen);nRes = SendDataBuff(pPacker->szBuff, nDataLen + RTP_HDR_LEN);if (nRes != (RTP_HDR_LEN + nDataLen)){printf(" udp send error !\n");return -1;}}else{nPlayLoadLen = RTP_MAX_PACKET_BUFF - RTP_HDR_LEN; // 每次只能发送的数据长度 除去rtp头gb28181_make_rtp_header(pPacker->szBuff, 0, ++pPacker->u16CSeq, pPacker->s64CurPts, pPacker->u32Ssrc);memcpy(pPacker->szBuff + RTP_HDR_LEN, databuff, nPlayLoadLen);nRes = SendDataBuff(pPacker->szBuff, RTP_HDR_LEN + nPlayLoadLen);if (nRes != (RTP_HDR_LEN + nPlayLoadLen)){printf(" udp send error !\n");return -1;}nDataLen -= nPlayLoadLen;// databuff += (nPlayLoadLen - RTP_HDR_LEN);databuff += nPlayLoadLen; // 表明前面到数据已经发送出去databuff -= RTP_HDR_LEN; // 用来存放rtp头while (nDataLen > 0){if (nDataLen <= nPlayLoadLen){//一帧数据发送完,置mark标志位gb28181_make_rtp_header(databuff, mark_flag, ++pPacker->u16CSeq, pPacker->s64CurPts, pPacker->u32Ssrc);nSendSize = nDataLen;}else{gb28181_make_rtp_header(databuff, 0, ++pPacker->u16CSeq, pPacker->s64CurPts, pPacker->u32Ssrc);nSendSize = nPlayLoadLen;}nRes = SendDataBuff(databuff, RTP_HDR_LEN + nSendSize);if (nRes != (RTP_HDR_LEN + nSendSize)){printf(" udp send error !\n");return -1;}nDataLen -= nSendSize;databuff += nSendSize;//因为buffer指针已经向后移动一次rtp头长度后,//所以每次循环发送rtp包时,只要向前移动裸数据到长度即可,这是buffer指针实际指向到位置是//databuff向后重复的rtp长度的裸数据到位置上}}return 0;
}//发送数据包
int SendDataBuff(char* buff, int size) {/* 设置address */struct sockaddr_in addr_serv;int len;memset(&addr_serv, 0, sizeof(addr_serv)); //memset 在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法addr_serv.sin_family = AF_INET;addr_serv.sin_addr.s_addr = inet_addr("192.168.30.166");addr_serv.sin_port = htons(20002);len = sizeof(addr_serv);int res = sendto(_socketFd, buff, size, 0, (struct sockaddr *)&addr_serv, len); //send函数专用于TCP链接,sendto函数专用与UDP连接。if (res != 0) {printf("res is %d\n", res);}return res;
}int gb28181_make_rtp_header(char *pData, int marker_flag, unsigned short cseq, long long curpts, unsigned int ssrc)
{bits_buffer_s bitsBuffer;if (pData == NULL)return -1;bitsBuffer.i_size = RTP_HDR_LEN;bitsBuffer.i_data = 0;bitsBuffer.i_mask = 0x80;bitsBuffer.p_data = (unsigned char *)(pData);memset(bitsBuffer.p_data, 0, RTP_HDR_LEN);bits_write(&bitsBuffer, 2, RTP_VERSION); /* rtp version */bits_write(&bitsBuffer, 1, 0); /* rtp padding */bits_write(&bitsBuffer, 1, 0); /* rtp extension */bits_write(&bitsBuffer, 4, 0); /* rtp CSRC count */bits_write(&bitsBuffer, 1, (marker_flag)); /* rtp marker */bits_write(&bitsBuffer, 7, 96); /* rtp payload type*/bits_write(&bitsBuffer, 16, (cseq)); /* rtp sequence */bits_write(&bitsBuffer, 32, (curpts)); /* rtp timestamp */bits_write(&bitsBuffer, 32, (ssrc)); /* rtp SSRC */return 0;
}int main(int argc, char** argv)
{if ((_socketFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {printf("创建套接字失败:");return -1;}int ul = 1;int ret = ioctl(_socketFd, FIONBIO, &ul); //设置为非阻塞模式if (ret == -1) {printf("设置非阻塞失败!");}Data_Info_s pPacker;pPacker.IFrame = 1;pPacker.u32Ssrc = 1234567890123;pPacker.s64CurPts = 0;FILE* fp = fopen(argv[1], "rb");char* buf = (char*)malloc(1024 * 1024);while (true) {int size = getNextNalu(fp, (unsigned char *)(buf + PES_HDR_LEN)); //PES_HDR_LEN 19 size:发送数据的长度if (size <= 0) {break;}gb28181_streampackageForH264(buf + PES_HDR_LEN, size, &pPacker, 0); //0 表示传递的是视频数据pPacker.s64CurPts += 3600;usleep(40 * 1000);}fclose(fp);return 0;
}
安利一个很好的github网址:
https://github.com/shenshuyu/es2ps
gb28181协议流媒体实现为rtp荷载ps流,将h264流打包成ps流。相关推荐
- H264和音频流打包成PS流 (MPEG2-PS)
技术在于交流.沟通,转载请注明出处并保持作品的完整性. 原文:https://blog.csdn.net/hiwubihe/article/details/80736848 [本系列相关文章] H26 ...
- 海康sdk捕获码流数据通过JavaCV推成rtmp流的实现思路(PS流转封装RTMP)
海康sdk捕获码流数据通过JavaCV推成rtmp流的实现思路(PS流转封装RTMP) 问题分析 转码推rtmp PS流转封装 码云(Gitee)主页:https://gitee.com/banmaj ...
- 如何解决国标GB28181协议流媒体视频平台新增ws_flv视频流无法播放问题?
流媒体服务器最主要的作用就是进行视频流的转码分发,比如EasyGBS视频平台,可通过国标GB28181协议将视频流转化成可播放的视频.目前EasyGBS可以输出RTMP.HLS.FLV三种格式的视频流 ...
- GB28181协议简介以及学习GB28181协议所涉及的名词汇总(入门篇)
1.GB/T 28181 -2016协议简介: 近年来,国内视频监控应用发展迅猛,系统接入规模不断扩大,涌现了大量平台提供商,平台提供商的接入协议各不相同,终端制造商需要给每款终端维护提供各种不同平台 ...
- 网络摄像头IPC国标GB28181协议国标安防视频流媒体平台EasyGBS视频流不上线排查步骤
近期使用国标GB28181协议平台的人越来越多,而我们的EasyGBS为了提升用户体验,近期做的升级也不少. 很多用户选择EasyGBS时,初期都由TSINGSEE青犀视频团队来进行产品的运维,日常运 ...
- PS封装格式:GB28181协议RTP传输
PS封装格式:GB28181协议RTP传输 1. GB28181要求的RTP流格式 2. 头部信息 在安防行业,有个协议是无论如何都要适配的,因为公安监控网络用的就是它,它就是:GB28181 ...
- RTP PS PES ES H264协议学习
参考:https://www.cnblogs.com/wainiwann/p/7477794.html https://blog.csdn.net/chenhande1990chenhan/artic ...
- GB28181协议实现系列之----IPC音视频PS封装(5)
RTP封装PS RTP报文头格式(见RFC3550 Page12): 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 12 3 4 5 6 7 8 9 0 1 + ...
- 最全流媒体协议详细总结介绍(RTP/RTCP/RTSP/RTMP/MMS/HLS/HTTP/ HTTP-FLV(HDL) /SDP)
RTP:实时传输协议(Real-time Transport Protocol) RTP是一种基于包的传输协议,它用来传输实时数据.在网络上传输数据包的延迟和误差是不可避免的,对此R ...
- 海康大华华为宇视等监控摄像头通过GB28181协议接入到LiveGBS流媒体平台如何给监控摄像头加水印...
监控摄像头.录像机或者平台通过GB28181协议接入到LiveGBS流媒体平台后,如果有加上特殊水印的需求,可以在通道配置里面自行配置. 1.编辑通道 LiveGBS管理页面-> 国标设备-&g ...
最新文章
- 基于vue和elementUI封装框选表格组件
- 更快更强,谷歌提出SWideRNet:全景分割新标杆来啦!
- 【前端_js】JavaScript知识点总结
- flutter环境搭建-完整版
- 分布式系统架构知识储备
- Interface Builder nib2objc ibtool
- CEikEdWin 类的使用
- html高德地图api使用教程,高德地图API如何使用?
- pycharm工具下代码下面显示波浪线的去处方法
- python中缀表达式转后缀表达式_中缀表达式转换成后缀表达式
- 利用DEEPLABV3-RESNET101获取人体蒙版
- 新浪微博爬取笔记(4):数据清理
- iOS 列表三级展开
- 小程序第三方平台初体验(上)|微专辑
- 产品管理包括什么和什么_什么是产品管理?
- 泰勒级数定义及相关展开式
- Python Tkinter详解 (二)Label标签的使用
- 进程的三种状态及转换
- 企业无线局域网的搭建
- 初学JAVA-8-对象和类、面向过程和面向对象