ps封裝及rtp/udp傳送
阿新 • • 發佈:2018-12-12
簡介
本文程式碼功能為,h264封裝為ps,再用rtp/udp傳送
程式碼
#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); while (!startCodeFound) { if (feof(inpf)) { 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); 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 / 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) { 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 [29..15]*/ bits_write(&bitsBuffer, 1, 1); /*marker bit*/ bits_write(&bitsBuffer, 9, lScrExt&0x01ff); /*System clock [14..0]*/ 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 /300), 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/100), 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/100), pPacker->u32Ssrc); nSendSize = nDataLen; } else { gb28181_make_rtp_header(databuff, 0, ++pPacker->u16CSeq, (pPacker->s64CurPts/100), 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)); addr_serv.sin_family = AF_INET; addr_serv.sin_addr.s_addr = inet_addr("127.0.0.1"); addr_serv.sin_port = htons(20002); len = sizeof(addr_serv); int res = sendto(_socketFd, buff, size, 0, (struct sockaddr *)&addr_serv, len); 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; FILE* fp = fopen(argv[1], "rb"); char* buf = (char*)malloc(1024 * 1024); while(true) { int size = getNextNalu(fp, (unsigned char *)(buf + PES_HDR_LEN)); if (size <= 0) { break; } gb28181_streampackageForH264(buf + PES_HDR_LEN, size, &pPacker, 0); pPacker.s64CurPts += 3600; usleep(40*1000); } fclose(fp); return 0; }
編譯:
gcc h264tortpoverps.cpp -g -o h264tortpoverps