RTP 将h264流封装成ps流并发送(C语言代码)

前言

本文只包含了代码,没有附上关于PS流的讲解。

文本的代码也是搜集了网上的代码,本文的创新点在于自己修改调试了代码,并且可以教大家如何修改代码可以轻松应用于自己的项目。

本文行文脉络为 -> 给出全文代码 -> 重点讲解代码如何修改 -> 应用于自己的项目

注: 本文大部分代码非原创,但是我一直找不到原创的链接,只有找到大家转载的链接,所以如果大家找到源代码请联系我,我修改文章附上链接。

全文代码

将全文代码复制到自己的项目后,先别急着运行,拉到下面看修改部分,修改后再编译运行。

#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];
};

typedef struct bits_buffer_s {
    unsigned char* p_data;
    unsigned char  i_mask;
    int i_size;
    int i_data;
}bits_buffer_s;

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]  压入的数值
 */
void bits_write(bits_buffer_s *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, struct 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, struct 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, struct 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("设置非阻塞失败!");
    }

    struct 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(1) {
        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;
}

修改处1 主函数套接字的绑定

创建好套接字后,如果想要设置规定的ip端口发送,可以调用bind函数,将自己的ip和端口绑定,这样就可以规范发送ip和端口。

修改处2 修改接收端口与ip

大家找到SendDataBuff函数,将这里的ip和端口修改一下,这样就规定了指定的ip和端口

addr_serv.sin_addr.s_addr = inet_addr("192.168.30.159");
addr_serv.sin_port = htons(20002);

修改处3 传入文件方式修改

大家可以看到,这个代码获取文件名的方式是直接在终端输入的方式

FILE* fp = fopen(argv[1], "rb");

我们可以讲argv[1]改为自己的文件名或者文件路径+文件名即可。这样在调试的时候会比较方便,如果需要灵活的变动播放视频那就不需要再改动了。

好了,本文讲解结束,若还有补充我会加更,若有什么错误请在评论区批评指正。感谢大家~

觉得有用就点个赞再走吧~

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
下面是一个简单的示例代码,使用C语言编写了RTP接收程序,并将接收到的数据组H264帧并保存为H264文件: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #define RTP_HEADER_SIZE 12 #define NALU_HEADER_SIZE 1 typedef struct rtp_header { uint8_t version:2; uint8_t padding:1; uint8_t extension:1; uint8_t csrc_count:4; uint8_t marker:1; uint8_t payload_type:7; uint16_t sequence_number; uint32_t timestamp; uint32_t ssrc; } rtp_header_t; int main(int argc, char *argv[]) { if (argc != 3) { printf("Usage: %s <rtp_port> <output_file>\n", argv[0]); return 1; } uint16_t port = atoi(argv[1]); char *output_file = argv[2]; FILE *fp = fopen(output_file, "wb"); if (fp == NULL) { printf("Failed to open output file!\n"); return 1; } int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock < 0) { printf("Failed to create socket!\n"); return 1; } struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; server.sin_port = htons(port); if (bind(sock, (struct sockaddr *)&server, sizeof(server)) < 0) { printf("Failed to bind socket!\n"); return 1; } uint8_t buffer[65536]; rtp_header_t *rtp_header; uint8_t *nalu_buffer = NULL; int nalu_size = 0; int nalu_type; int last_nalu_end = 0; while (1) { ssize_t bytes_received = recvfrom(sock, buffer, sizeof(buffer), 0, NULL, NULL); if (bytes_received < RTP_HEADER_SIZE + NALU_HEADER_SIZE) { printf("Invalid RTP packet!\n"); continue; } rtp_header = (rtp_header_t *)buffer; uint8_t *payload = buffer + RTP_HEADER_SIZE; int payload_size = bytes_received - RTP_HEADER_SIZE; if (nalu_buffer == NULL) { // Look for the start of an H.264 NAL unit for (int i = 0; i < payload_size - 4; i++) { if ((payload[i] == 0x00) && (payload[i+1] == 0x00) && (payload[i+2] == 0x00) && (payload[i+3] == 0x01)) { // Found the start of a NAL unit nalu_buffer = payload + i + 4; nalu_size = payload_size - i - 4; nalu_type = nalu_buffer[0] & 0x1F; break; } } } else { // Look for the end of the current NAL unit int new_nalu_end = 0; for (int i = 0; i < payload_size - 4; i++) { if ((payload[i] == 0x00) && (payload[i+1] == 0x00) && (payload[i+2] == 0x00) && (payload[i+3] == 0x01)) { // Found the start of a new NAL unit new_nalu_end = i; break; } } if (new_nalu_end > 0) { // This packet contains the end of the current NAL unit fwrite(nalu_buffer, nalu_size, 1, fp); // Start assembling the next NAL unit nalu_buffer = payload + new_nalu_end + 4; nalu_size = payload_size - new_nalu_end - 4; nalu_type = nalu_buffer[0] & 0x1F; // Update the last NAL unit end position last_nalu_end = new_nalu_end; } else { // This packet contains part of the current NAL unit fwrite(nalu_buffer, RTP_HEADER_SIZE + NALU_HEADER_SIZE, 1, fp); fwrite(payload, payload_size, 1, fp); nalu_size += payload_size; } } } fclose(fp); return 0; } ``` 该程序将从指定的RTP端口接收数据,并将接收到的数据组H264帧并保存到指定的文件中。程序使用了一个简单的技巧来查找H264的NAL单元边界,也就是4个0字节后紧跟着一个1字节。程序的核心是一个无限循环,该循环将不断接收RTP数据包并处理它们。 请注意,这只是一个非常简单的示例程序,可能无法处理所有情况。在实际应用中,您需要考虑更多的细节和错误情况,并进行适当的错误处理和恢复。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值