《音视频:从RTP数据包中解析H264裸流》

一,前言

本文旨在从传输h264编码的RTP数据包中,解析出h264裸流数据。对于传输h264数据的rtp数据包,一般有三种类型,单包、单一时间的组合包(STAP-A)、FU-A分片包。本文提取出这三种类型的数据,保存到文件中,得到的h264裸流文件可以通过ffmpeg播放。

二,RTP 数据包格式

2.1 RTP 包头

在这里插入图片描述

V:版本号,一般为2,2bits
P:填充,1bits
X:扩展,1bits
CC:CSRC计数,4bits
M:标志,1bits
PT:负载类型,7bits。对于H264一般值为100
sequence number:序列号,16bits
timestamp:时间戳,32bits
SSRC:同步源标志,32bits
CSRC:一般不用。所以,RTP数据包头一般为12个字节(96bits)。

通过wireshark抓包可以看到如下rtp 包头信息
在这里插入图片描述

2.2 RTP负载

rtp负载就是传输的h264的裸流数据,现在要做的就是要从这些负载中还原出h264的裸流数据。

三,RTP包负载中H264数据格式

RTP包传输中的H264数据,由NALU Header+NALU Payload组成。对于NALU Header,单包、组合包的NALU Header和分片包的NALU Header又有所不同。单包、分片包的NALU Payload和组合包的负载又有所不同。

3.1 RTP包中的H264数据类型

NAl type NALU 单元类型
1-23 NAL单元 单个NALU包
24 STAP-A 单一时间的组合包
28 FU-A 分片的单元

3.2 NALU单元的类型

1 非IDR图像中不采用数据划分的片段
2 非IDR图像中A类数据划分片段
3 非IDR图像中B类数据划分片段
4 非IDR图像中C类数据划分片段
5 IDR图像的片段
6 补充增强信息 (SEI)
7 序列参数集(sps)
8 图像参数集(PPS)
9 分割符
10 序列结束符
11 流结束符
12 填充数据

3.3 单包和组合包的NALU Header

一个RTP包中去掉上面12个字节的包头,第十三个字节就是NALU Header,主要看它的后五位,“00111”表示该H264数据的类型,也就是该NALU单元的类型。
在这里插入图片描述

3.4 分片包的NALU Header

分片包的NALU Header由FU identifier+FU Header组成。一个RTP包中去掉上面12个字节的包头,第十三个字节就是FU identifier,后五位“00111”表示该H264数据包的类型。第十四个字节就是FU Header,其后五位“00001”表示NALU单元的类型。最终由FU identifier的前三位,和FU Header的后五位组成写入裸流文件中的一个NALU Header。 分片包又有,起始包、中间包和结束包。可以由RTP头中的M标志判断该包是否为结束包,M为1表示结束包,M为0表示可能为起始包或中间包,再由FU Header的第七位(0位起算)判断其是否为起始包。也可以通过FU Header的第七、第六位(0位起算)判断,10为起始包,00为中间包,01为结束包。
在这里插入图片描述

3.5 单包和分片包的NALU Payload

单包的NALU Payload就是这个NALU单元的数据。多个分片包的Payload共同组成一个NALU单元的数据。
在这里插入图片描述

3.6 组合包的NALU Payload

组合包中存在这多个NALU单元数据,需要分别提取出来,加上起始码“00 00 00 01”组成一个个NALU单元写入文件。
在这里插入图片描述
在这里插入图片描述
比如有这么一段RTP包,“80 64 94 1d 00 04 e3 3c 9d 9c 66 fc”为RTP包头。78为NAL Header,其后五位“11000”= 24表示该包为组合包。“00 0f”表示第一个NALU单元的长度(NALU header+payload),“67 42 c0 29 43 23 50 16 87 a4 03 c2 21 1a 80” ,67为NALU header,其后五位“00111”表示该NALU单元为sps类型。"00 04"表示第二个NALU单元的长度(NALU header+payload)“68 48 e3 c8”68为NALU header,其后五位“01000”表示该NALU单元为PPS类型。

/*
        80 64 94 1d 00 04 e3 3c 9d 9c 66 fc
        78 00 0f 67 42 c0 29 43 23 50 16 87
        a4 03 c2 21 1a 80 00 04 68 48 e3 c8
*/

四,文件中的H264数据格式

对于h264裸流文件,其由一个个的NALU单元组成,NALU单元由00 00 00 01的起始码+NALU Header+NALU数据组成。

五,解包生成h264裸流文件程序

最后生成的receive.h264文件,直接使用ffmplay receive.h264播放即可。

#define PARESERTP
#define TEST_PARESERTP

#ifdef TEST_PARESERTP
static FILE* poutfile = NULL;
const char* outputfilename = "G:\\receive.h264";

int initTest()
{
    
    
    poutfile = fopen(outputfilename, "ab+");
    if (!poutfile)
    {
    
    
        return -1;
    }
    return 0;
}

void uninitTest()
{
    
    
    if (poutfile)
    {
    
    
        fclose(poutfile);
        poutfile = NULL;
    }
}

#endif

#ifdef PARESERTP

#define DC_PRINT_DEBUG qDebug() << __FUNCTION__ << __LINE__
#define  MAXDATASIZE 2048


typedef struct
{
    
    
    unsigned char version;          	//!< Version, 2 bits, MUST be 0x2
    unsigned char padding;			 	//!< Padding bit, Padding MUST NOT be used
    unsigned char extension;			//!< Extension, MUST be zero
    unsigned char cc;       	   		//!< CSRC count, normally 0 in the absence of RTP mixers 		
    unsigned char marker;			   	//!< Marker bit
    unsigned char pt;			   		//!< 7 bits, Payload Type, dynamically established
    unsigned int seq_no;			   	//!< RTP sequence number, incremented by one for each sent packet 
    unsigned int timestamp;	       //!< timestamp, 27 MHz for H.264
    unsigned int ssrc;			   //!< Synchronization Source, chosen randomly
    unsigned char* payload;      //!< the payload including payload headers
    unsigned int paylen;		   //!< length of payload in bytes
} RTPpacket_t;

typedef struct
{
    
    
    /* byte 0 */
    unsigned short csrc_len : 4;        /* expect 0 */
    unsigned short extension : 1;       /* expect 1, see RTP_OP below */
    unsigned short padding : 1;         /* expect 0 */
    unsigned short version : 2;         /* expect 2 */
   /* byte 1 */
    unsigned short payloadtype : 7;     /* RTP_PAYLOAD_RTSP */
    unsigned short marker : 1;          /* expect 1 */
   /* bytes 2,3 */
    unsigned short seq_no;
    /* bytes 4-7 */
    unsigned int timestamp;
    /* bytes 8-11 */
    unsigned int ssrc;              /* stream number is used here. */
} RTP_FIXED_HEADER;


typedef struct
{
    
    
    unsigned char forbidden_bit;           //! Should always be FALSE
    unsigned char nal_reference_idc;       //! NALU_PRIORITY_xxxx
    unsigned char nal_unit_type;           //! NALU_TYPE_xxxx  
    unsigned int startcodeprefix_len;      //! 前缀字节数
    unsigned int len;                      //! 包含nal 头的nal 长度,从第一个00000001到下一个000000001的长度
    unsigned int max_size;                 //! 做多一个nal 的长度
    unsigned char* buf;                   //! 包含nal 头的nal 数据
    unsigned int lost_packets;             //! 预留
} NALU_t;

typedef struct
{
    
    
    //byte 0
    unsigned char TYPE : 5;
    unsigned char NRI : 2;
    unsigned char F : 1;
} NALU_HEADER; // 1 BYTE 


typedef struct
{
    
    
    //byte 0
    unsigned char TYPE : 5;
    unsigned char NRI : 2;
    unsigned char F : 1;
} FU_INDICATOR; // 1 BYTE 


typedef struct
{
    
    
    //byte 0
    unsigned char TYPE : 5;
    unsigned char R : 1;
    unsigned char E : 1;
    unsigned char S : 1;
} FU_HEADER;   // 1 BYTES 


NALU_t* AllocNALU(int buffersize)
{
    
    
    NALU_t* n;

    if ((n = (NALU_t*)calloc(1, sizeof(NALU_t))) == NULL)
    {
    
    
        //DC_PRINT_DEBUG<<Str.sprintf("AllocNALU Error: Allocate Meory To NALU_t Failed ");
        exit(0);
    }
    return n;
}

void FreeNALU(NALU_t* n)
{
    
    
    if (n)
    {
    
    
        free(n);
    }
}


void rtp_unpackage(char* bufIn, int len, BYTE* outbuff, unsigned int* dataLen)
{
    
    
    unsigned char recvbuf[1500] = {
    
     0 };
    RTPpacket_t* p = NULL;
    RTP_FIXED_HEADER* rtp_hdr = NULL;
    NALU_HEADER* nalu_hdr = NULL;

    FU_INDICATOR* fu_ind = NULL;
    FU_HEADER* fu_hdr = NULL;
    int total_bytes = 0;
    static int total_recved = 0;
    
    QString Str;
    static BYTE byHeadLong[] = {
    
     0x00, 0x00, 0x00, 0x01 };
    int offest = 0;

    memcpy(recvbuf, bufIn, len);          //复制rtp包    

    //begin rtp_payload and rtp_header
    if ((p = (RTPpacket_t*)malloc(sizeof(RTPpacket_t))) == NULL)
    {
    
    
        DC_PRINT_DEBUG << Str.sprintf("RTPpacket_t MMEMORY ERROR\n");
    }

    if ((p->payload = (unsigned char*)malloc(MAXDATASIZE)) == NULL)
    {
    
    
        DC_PRINT_DEBUG << Str.sprintf("RTPpacket_t payload MMEMORY ERROR\n");
    }

    if ((rtp_hdr = (RTP_FIXED_HEADER*)malloc(sizeof(RTP_FIXED_HEADER))) == NULL)
    {
    
    
        DC_PRINT_DEBUG << Str.sprintf("RTP_FIXED_HEADER MEMORY ERROR\n");
    }

    // rtp_hdr =(RTP_FIXED_HEADER*)&recvbuf[0]; 
    memcpy((void*)rtp_hdr, (void*)&recvbuf[0], sizeof(RTP_FIXED_HEADER));

    p->version = rtp_hdr->version;
    p->padding = rtp_hdr->padding;
    p->extension = rtp_hdr->extension;
    p->cc = rtp_hdr->csrc_len;
    p->marker = rtp_hdr->marker;
    p->pt = rtp_hdr->payloadtype;
    p->seq_no = rtp_hdr->seq_no;
    p->timestamp = rtp_hdr->timestamp;
    p->ssrc = rtp_hdr->ssrc;
    // DC_PRINT_DEBUG << Str.sprintf("my rtp decode versoin: %d,payload type: 0x%x,seq number: 0x%x,timestamp: 0x%x,ssrc: 0x%x\n",
    //     rtp_hdr->version, rtp_hdr->payloadtype, rtp_hdr->seq_no, rtp_hdr->timestamp, rtp_hdr->ssrc);

    //end rtp_payload and rtp_header
    //
    //begin nal_hdr

    if ((nalu_hdr = (NALU_HEADER*)malloc(sizeof(NALU_HEADER))) == NULL)
    {
    
    
        DC_PRINT_DEBUG << Str.sprintf("NALU_HEADER MEMORY ERROR\n");
    }

    memcpy((void*)nalu_hdr, (void*)&recvbuf[12], 1);

    // DC_PRINT_DEBUG << Str.sprintf("forbidden_zero_bit: %d\n", nalu_hdr->F);
    // DC_PRINT_DEBUG << Str.sprintf("nal_reference_idc:  %d\n", nalu_hdr->NRI);
    // DC_PRINT_DEBUG << Str.sprintf("nal type: %d\n", nalu_hdr->TYPE);

    //end nal_hdr

    //开始解包 对于h264 CB 有三种类型,单包、STAP-A组合包以及FU-A分包
    if (nalu_hdr->TYPE == 0)
    {
    
    
        DC_PRINT_DEBUG << Str.sprintf("pkt error no 0 type\n");
    }
    else if (nalu_hdr->TYPE > 0 && nalu_hdr->TYPE < 24)  //单包 0x00 < type < 0x18
    {
    
    
        DC_PRINT_DEBUG << Str.sprintf("cur pkt is singal\n");

#ifdef TEST_PARESERTP
        putc(0x00, poutfile);
        putc(0x00, poutfile);
        putc(0x00, poutfile);
        putc(0x01, poutfile);	//写进起始字节0x00000001
#endif
        memcpy(outbuff + total_bytes, byHeadLong, sizeof(byHeadLong));
        total_bytes += sizeof(byHeadLong);

#ifdef TEST_PARESERTP
        if(poutfile)
            fwrite(nalu_hdr, 1, 1, poutfile);	//写NAL_HEADER
#endif
        memcpy(outbuff + total_bytes, nalu_hdr, sizeof(NALU_HEADER));
        total_bytes += sizeof(NALU_HEADER);

        memcpy(p->payload, &recvbuf[13], len - 13);
        p->paylen = len - 13;

#ifdef TEST_PARESERTP
        if(poutfile)
            fwrite(p->payload, 1, p->paylen, poutfile);	//写NAL数据
#endif
        memcpy(outbuff + total_bytes, p->payload, len - 13);
        total_bytes += p->paylen;

    }
    else if (nalu_hdr->TYPE == 24)                    //STAP-A   单一时间的组合包  0x18
    {
    
    
        int pktLen = len - 13;
        /*
        80 64 94 1d 00 04 e3 3c 9d 9c 66 fc
        78 00 0f 67 42 c0 29 43 23 50 16 87
        a4 03 c2 21 1a 80 00 04 68 48 e3 c8
        */
        unsigned short naluSize;
        unsigned int nextNaluSizeIndex = 13;
        while (pktLen)
        {
    
    
            naluSize = recvbuf[nextNaluSizeIndex] << 8 | recvbuf[nextNaluSizeIndex + 1];

#ifdef TEST_PARESERTP            
            putc(0x00, poutfile);
            putc(0x00, poutfile);
            putc(0x00, poutfile);
            putc(0x01, poutfile);
#endif
            memcpy(outbuff + total_bytes, byHeadLong, sizeof(byHeadLong));
            total_bytes += sizeof(byHeadLong);
#ifdef TEST_PARESERTP
            if(poutfile)
                fwrite(&recvbuf[nextNaluSizeIndex + 2], naluSize, 1, poutfile);
#endif
            memcpy(outbuff + total_bytes, &recvbuf[nextNaluSizeIndex + 2], naluSize);
            total_bytes += naluSize;

            nextNaluSizeIndex = nextNaluSizeIndex + naluSize + 2;
            pktLen = pktLen - naluSize - 2;            
        }
    }
    else if (nalu_hdr->TYPE == 28 || nalu_hdr->TYPE == 29)                     //FU-A、FU-B分片包
    {
    
    
        if ((fu_ind = (FU_INDICATOR*)malloc(sizeof(FU_INDICATOR))) == NULL)
        {
    
    
            DC_PRINT_DEBUG << Str.sprintf("FU_INDICATOR MEMORY ERROR\n");
        }
        if ((fu_hdr = (FU_HEADER*)malloc(sizeof(FU_HEADER))) == NULL)
        {
    
    
            DC_PRINT_DEBUG << Str.sprintf("FU_HEADER MEMORY ERROR\n");
        }

        // fu_ind=(FU_INDICATOR*)&recvbuf[12];		//分片包用的是FU_INDICATOR而不是NALU_HEADER
        memcpy((void*)fu_ind, (void*)&recvbuf[12], 1);  // 0x7c

        
        // DC_PRINT_DEBUG << Str.sprintf("FU_INDICATOR->F     :%d\n", fu_ind->F);
        // DC_PRINT_DEBUG << Str.sprintf("FU_INDICATOR->NRI   :%d\n", fu_ind->NRI);
        // DC_PRINT_DEBUG << Str.sprintf("FU_INDICATOR->TYPE  :%d\n", fu_ind->TYPE);


        // fu_hdr=(FU_HEADER*)&recvbuf[13];		//FU_HEADER赋值
        memcpy((void*)fu_hdr, (void*)&recvbuf[13], 1);  //0x85
        
        // DC_PRINT_DEBUG << Str.sprintf("FU_HEADER->S        :%d\n", fu_hdr->S);
        // DC_PRINT_DEBUG << Str.sprintf("FU_HEADER->E        :%d\n", fu_hdr->E);
        // DC_PRINT_DEBUG << Str.sprintf("FU_HEADER->R        :%d\n", fu_hdr->R);
        // DC_PRINT_DEBUG << Str.sprintf("FU_HEADER->TYPE     :%d\n", fu_hdr->TYPE);


        if (rtp_hdr->marker == 1)                      //分片包最后一个包
        {
    
    
            DC_PRINT_DEBUG << Str.sprintf("cur pkt is FU-A laster pkt\n");

            memcpy(p->payload, &recvbuf[14], len - 14);
            p->paylen = len - 14;
#ifdef TEST_PARESERTP
            if(poutfile)
                fwrite(p->payload, 1, p->paylen, poutfile);	//写NAL数据
#endif
            memcpy(outbuff + total_bytes, p->payload, p->paylen);
            total_bytes += p->paylen;
            DC_PRINT_DEBUG << Str.sprintf("payload len = %d\n", p->paylen);

            // 保存所有的fu-a分包的数据到内存
            memcpy(fuData + fuDataLen, outbuff, total_bytes);
            fuDataLen += total_bytes;
            fuReviceEnd = 1; // 接收到所有的FU分包数据
        }
        else if (rtp_hdr->marker == 0)                 //分片包 但不是最后一个包
        {
    
    
            if (fu_hdr->S == 1)                        //分片的第一个包
            {
    
    
                unsigned char F;
                unsigned char NRI;
                unsigned char TYPE;
                unsigned char nh;
                DC_PRINT_DEBUG << Str.sprintf("cur pkt is FU-A first pkt\n");

#ifdef TEST_PARESERTP                
                putc(0x00, poutfile);
                putc(0x00, poutfile);
                putc(0x00, poutfile);
                putc(0x01, poutfile);
#endif
                memcpy(outbuff + total_bytes, byHeadLong, sizeof(byHeadLong));
                total_bytes += sizeof(byHeadLong);

                F = fu_ind->F << 7;
                NRI = fu_ind->NRI << 5;
                TYPE = fu_hdr->TYPE;
                nh = F | NRI | TYPE;   //NAL HEADER
#ifdef TEST_PARESERTP
                putc(nh, poutfile);
#endif
                memcpy(outbuff + total_bytes, &nh, 1);
                total_bytes += 1;

                memcpy(p->payload, &recvbuf[14], len - 14);
                p->paylen = len - 14;
#ifdef TEST_PARESERTP                
                if(poutfile)
                    fwrite(p->payload, 1, p->paylen, poutfile);
#endif
                memcpy(outbuff + total_bytes, p->payload, p->paylen);
                total_bytes += p->paylen;

                // DC_PRINT_DEBUG << Str.sprintf("payload len = %d\n", p->paylen);
                // 保存所有的fu-a分包的数据到内存
                memset(fuData, 0, MAXFREAMLEN);
                isFU = 1; //接收到第一个分包
                memcpy(fuData + fuDataLen, outbuff, total_bytes);
                fuDataLen += total_bytes;

            }
            else                                      //分片的中间包
            {
    
    
                memcpy(p->payload, &recvbuf[14], len - 14);
                p->paylen = len - 14;
#ifdef TEST_PARESERTP                
                if(poutfile)
                    fwrite(p->payload, 1, p->paylen, poutfile);
#endif
                memcpy(outbuff + total_bytes, p->payload, p->paylen);
                total_bytes += p->paylen;
                // DC_PRINT_DEBUG << Str.sprintf("payload len = %d\n", p->paylen);

                // 保存所有的fu-a分包的数据到内存
                memcpy(fuData + fuDataLen, outbuff, total_bytes);
                fuDataLen += total_bytes;
            }
        }
    }
    else
    {
    
    
        DC_PRINT_DEBUG << Str.sprintf("this pkt is error 30-31 no define\n");
    }

    // DC_PRINT_DEBUG<<"rtp decode total_bytes: "<<total_bytes;
    *dataLen = total_bytes;
    total_bytes = 0;
    memset(recvbuf, 0, 1500);
    free(fu_ind);
    free(fu_hdr);
    free(nalu_hdr);
    free(rtp_hdr);
    free(p->payload);
    free(p);

    //结束解包
    //
    return;
}
#endif

猜你喜欢

转载自blog.csdn.net/qq_40709487/article/details/127132833