ps封装及rtp/udp发送

GB28181利用RTP协议推送264的PS封包音视频 实现满足GB28181国标的设备基本流程(参看上一篇博客链接)之后,收到实时音视频点播请求后,需要进行RTP协议推送音视频。 从sip解析到invite和ack消息,可以拿到流媒体接收的ip地址和port端口,之后利用socket进行udp发送音视频数据RTP包到接收服务端。 发送RTP包,需要了解RTP协议,以及RTP数据定义。另外,GB28181国标要求发送264的PS封包RTP负载,因此,还... 阅读详情

简介

    本文代码功能为,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, 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));  
  addr_serv.sin_family = AF_INET;  
  addr_serv.sin_addr.s_addr = inet_addr("192.168.30.159");  
  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;
  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));
    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
vlc播放:

首先,要编写一个sdp文件,供vlc进行播放

m=video 20002 RTP/AVP 96
a=rtpmap:96 MP2P/90000
a=framerate:25
c=IN IP4 192.168.30.159
然后用vlc打开这个sdp文件就可以了。

GB28181流媒体开发实战:RTP/PS封装与Wireshark抓包深度解析 在实时音视频传输领域,RTP(实时传输协议)是承载媒体流的核心传输层协议,它通过序列号和时间戳机制应对网络抖动与乱序,为实时性提供基础保障。其技术价值在于为视频会议、安防监控等低延迟应用提供了标准化的传输框架。在实际工程中,原始编码数据通常需要进一步封装,例如GB28181标准普遍采用的PS(节目流)格式,它将音视频流复用并加入时间戳,形成自包含的传输单元。应用场景广泛覆盖安防监控、视频会议等需要可靠媒体传输的领域。本文聚焦于GB28181协议栈,深入剖析RTP负载中PS封装的结构与重组逻辑,并手把手演示如 阅读详情

相关推荐

流媒体——RTP 基于UDP和TCP协议传输数据——初探

1、RTP over RTSP(TCP,RTP包通过使用TCP来传输)优势: UDP协议上的RTSP/RTP需要打开许多UDP端口,一个端口用于RTSP通信,n个端口用于RTP,n个端口用于RTCP 中间网络路由器很容易就过滤或者忽略掉UDP数据包 UDP是不可靠传输协议,媒体包在因特网上传输时会面临着丢包 2、RTP over RTSP(TCP) 与 RTP over RTSP(UDP) RTP包头差异: rtp基于tcp的包头比基于udp的包头多了4个字节: magic固定为0x24...

weixiao的博客 4830

音视频数据封装——PS格式的封装与私有数据显示延迟问题(源码分析)

目录 一、引言 二、PS流 ------> 2.1、PS封装 ------> 2.2、头部分析 三、相关代码 ------> 3.1、音视频帧 ------> 3.1、私有数据(人形框) 一、引言 两个月没写博客了,这段时间家里事比较多,就偷了个懒,今天开始会陆陆续续开始恢复博客的更新! 这两天在看PS流的打包这块,因为遇到了一个延迟问题,所以就来系统性的分析一下PS封装的具体实现过程 二、PS流 之前的文章介绍过PS流的大致格式,但感觉不是很详细,这里来简单回顾下 PS封装

文艺小少年的博客 856

C++基于jrtp实现rtp发送与接收代码实现(附源码)

本文是C/C++常用功能代码封装专栏的导航贴。部分来源于实战项目中的部分功能提炼,希望能够达到你在自己的项目中拿来就用的效果,这样更好的服务于工作实践。专栏介绍:专栏讲本人近10年后端开发常用的案例,以高质量的代码提取出来,并对其进行了介绍。代码拿去可以直接使用的,可以解决C++常见的问题。专栏使用人群:C++软件开发人员通过上面的例子,你应该对jrtp的使用有了较深刻的认识,希望对你。

weixin_44834554的博客 1158

国标GB28181对接视频流(C++实现,有源码)

国标GB28181对接视频流 今天抽空写下以GB28181的方式获取摄像机视频流以备后用,同时也希望能帮助到正着手开发GB28181对接视频的同学,这块的资料实在不多。 今天讲的内容不涉及到平台对接,平台对接下次有时间再讲,平台对接相对更麻烦点。通过GB28181获取摄像机视频流,首先需要摄像机支持GB28181 ,如何知道摄像机是否支持GB28181协议呢?请看下图: ...

QQ_976606456的博客 6485

H264的RTP封包

H264流结构 H264的ES流的结构分为两层,包括视频编码层(VCL)和网络适配层(NAL)。视频编码层负责视频内容表示,而网络适配层负责对数据进行打包和传送。NAL和VCL分离主要有以下两方面的好处: 1、信号处理和网络传输分层,VCL和NAL可以在不同的处理平台上实现; 2、VCL和NAL分离设计,可以在不同的网络环境内,网关不需要因为网络环境不同而对VCL比特流进行...

qq_15906821的博客 705

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。对于..

孤独行者的专栏 4395

GB28181的PS流分析: 封装 / 分包发送 / 接收组包 / 解析

GB28181的PS流分析: 封装 / 分包发送 / 接收组包 / 解析

cry_smile_hate的专栏 3342

GB28181设备端PS封装发送

GB/T28181视频监控系统中设备端PS封装RTP传输技术解析 本文详细介绍了GB/T28181标准下设备端实时视频流的封装与传输技术要点。主要内容包括: 整体流程:从编码数据准备到PS封装RTP打包及发送的完整链路; PS封装规范:重点解析PS头(14B)、系统头(18B)、节目流映射(24B)和PES头(19B)的结构与生成方法; 关键代码实现:提供RTP/PS/PES头部构造的核心代码示例; 常见问题排查:包括服务器无法解析、花屏、音频异常等问题的解决方法。 文章特别强调时间戳对齐、TCP模

程序猿老樊的博客 186

RTP载荷PS流全面分析

1、PS流封包格式 视频关键帧的封装RTP|PS header|PS system header|PS system Map|PES header|H264 data 视频非关键帧的封装RTP|PS header|PES header|H264data 音频帧的封装RTP|PES header|G711 2、服务端PS流接收组包和解析 以海康DS-IPC-B12H2-I为例 (1)rtp over udp 80 60 00 00 00 00 00 00 00 00 04 00 00 0.

orgotF的博客 2907

RTSP、RTCP、RTP区别

1.RTP over UDPRTP over RTSP有什么区别? 不一样, RTP over UDPRTP下层使用udp传输, RTP over RTSP 是指的用rtsp协议建立会话,然后使用RTP协议传输数据; 2.RTP over RTSP是不是就是RTP over TCP? 不是:RTP over RTSP 是指的用用rtsp协议建立会话,然后使用RTP协议传输数据; 至于下...

宁静致远 1万+

GB28181的PS流完全分析(封装 / 分包发送 / 接收组包 / 解析)

一、PS流传输格式预览 1、视频关键帧的封装 RTP + PS header + PS system header + PS system Map + PES header +h264 data 2、视频非关键帧的封装 RTP +PS header + PES header + h264 data 3、音频帧的封装: RTP + PES header + G711 二、服务端PS流接收组包和解析 以海康DS-IPC-B12H2-I为例 rtp over udp 80 60 00 00 00 00 0

孤独行者的专栏 3279

ps流组成 PS封装PS+SYS+PSM+PES+RAW )概述

在工作中,招标时要对设备进行gb28181检测,需要对音视频数据进行封装封装方式为PS流,封装实现如下: 简要概述:整体机制均有udp发送发送的数据要封装rtp格式,rtp负载即为PS流。 PS流: PS流由众多PS包构成,每个PS包的基本构成形式为: PS Header + SYS Header + PSM Header + PES Header + PES packet * N 固定包...

weixin_43608153的博客 9633

RTP、RTSP和ES、PES、PS以及TS码流的区别

ES、PES、PS以及TS区别 基本概念 ES ES–Elementary Streams (原始流)是直接从编码器出来的数据流,可以是编码过的视频数据流(H.264,MJPEG等),音频数据流(AAC),或其他编码数据流的统称。ES流经过PES打包器之后,被转换成PES包。 ES是只包含一种内容的数据流,如只含视频或只含音频等,打包之后的PES也是只含一种性质的ES,如只含视频ES的PES,...

学无止尽,谨言慎行! 1万+

PSPS OVER RTP

封包,分片,发送

突围 521

H264视频流转RTP封装与传输实践

H264,也被称为高级视频编码(AVC),是一种视频压缩标准,主要用于高效视频传输和存储。它作为MPEG-4标准的一部分,已成为现代视频应用中的主流技术,特别是在高清视频、流媒体及电视广播等领域。H264的压缩效率高于它的前代标准H263和MPEG-2,这使得它在带宽受限的环境下同样能提供高质量的视频。RTP,即实时传输协议(Real-time Transport Protocol),是互联网协议族中用于传输音频和视频数据的应用层协议。

weixin_28235889的博客 1196

GB28181协议下的RTP传输

RTP(Real-Time Transport Protocol)基于RFC 3550,专为实时音视频传输设计,通常运行于UDP或TCP之上,结合RTCP实现质量控制。:通过NTP时间戳和RTP时间戳对齐音视频流。:根据网络状况动态调整编码码率或分片策略。两种传输层协议,TCP分为。

_程序猿 2052
上一篇: GB28181的PS流完全分析(封装 / 分包发送 / 接收组包 / 解析)
下一篇: DZ先生怪谈GB28181-2016之SDP
NBA_1
博客等级 码龄15年 59粉丝 34原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值