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)的取值如下:
1. GB28181要求的RTP流格式
首先,我们来看看I帧的PS流格式,这里需要注意的是SPS、PPS之前要加上PES头部。如下图所示,其中绿色部分就是我们拿到的H.264裸流数据,须将它拆分成三段并在前面加上PES头部。这一点在GB28181标准中没有细说,需要通过分析海康IPC流才能看出。
一般情况下IDR帧很大,超过了RTP的负载长度限制(1400字节),所以上面这一个I帧要拆分成若干包RTP分多次发送。第一包的结构如上图所示,第二包以后RTP的结构就简单多了,它是这样的:
上面提到的是I帧的情况,相比它,P/B帧的帧格式真是太简单了,因为它既没有SYS、PSM,也没有SPS、PPS:
P/B帧大小一般不超过1400字节,如果超过1400字节,也需分成多包RTP数据进行传输,超出1400部分的第二包RTP结构:
关于时间戳的比特位结构,下图表示的比较清晰:
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 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, 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标志位置1
gb28181_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 65522
union 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:读取成功时返回读取到的字符,读取到文件末尾或读取失败时返回EOF
while (!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 1
return 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 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, 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标志位置1
gb28181_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;
}