hi3516a——H.264數據包 封包爲 RTP數據包(附封包源碼和詳細解析)

前言

      ~~~~~~由於調試HI3516A進行RTP流媒體播放時,需要清楚怎麼把H.264數據包 封包爲 RTP數據包並發出去。本文章將詳細解析H.264數據包 封包爲 RTP數據包的協議格式和源代碼。
硬件平臺:hi3516a
軟件平臺:Hi3516A_SDK_V1.0.5.0

      ~~~~~~H.264數據包 封包爲 RTP數據包,網上找了很多資料,但是都不全,所以我嘗試結合實例整理出一個比較全面的解析,結合具體的實例也更容易理解些。文章借鑑了很多文章,我都列在了文章最後,在此表示感謝。
      ~~~~~~無私分享,從我做起!

H.264數據包 封包爲 RTP數據包的源代碼

下面是H.264數據包 封包爲 RTP數據包的源代碼,這裏面加了很多打印信息,不需要的可以自己去掉。

/**************************************************************************************************

 RTSP(Real Time Streaming Protocol),RFC2326

 RTP :Real Time Protocol 實時協議

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X|  CC   |M|     PT      |       sequence number         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           timestamp                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           synchronization source (SSRC) identifier            |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|            contributing source (CSRC) identifiers             |
|                             ....                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
|                                                               |
|                                                               |
|               payload                                         |
|                                                               |
|                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                               :...OPTIONAL RTP padding        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+



************************************************************************************************/

extern unsigned char sps_tmp[256];
extern unsigned char pps_tmp[256];
extern int sps_len;
extern int pps_len;

static int SendNalu264(HndRtp hRtp, char *pNalBuf, int s32NalBufSize)
	
{

	printf("input H.264 raw data----count=%ld------\r\n",s32NalBufSize);
	int i=0;
	printf("0x");
	while(i<100)
	{
		printf("%x ",pNalBuf[i]);
		i++;
	}
	printf("......\r\n");

	

    char *pNaluPayload;
    char *pSendBuf;
    int s32Bytes = 0;
    int s32Ret = 0;
    struct timeval stTimeval;
    char *pNaluCurr;
    int s32NaluRemain;
    unsigned char u8NaluBytes;
    pSendBuf = (char *)calloc(MAX_RTP_PKT_LENGTH + 100, sizeof(char));  //#define MAX_RTP_PKT_LENGTH     1400
    if(NULL == pSendBuf)
    {
        s32Ret = -1;
        goto cleanup;
    }

    hRtp->pRtpFixedHdr = (StRtpFixedHdr *)pSendBuf;
    hRtp->pRtpFixedHdr->u7Payload   = H264;
    hRtp->pRtpFixedHdr->u2Version   = 2;
    hRtp->pRtpFixedHdr->u1Marker    = 0;
    hRtp->pRtpFixedHdr->u32SSrc     = hRtp->u32SSrc;
    //計算時間戳
    hRtp->pRtpFixedHdr->u32TimeStamp = htonl(hRtp->u32TimeStampCurr * (90000 / 1000));
    printf("timestamp:%lld\n",hRtp->u32TimeStampCurr);
    if(gettimeofday(&stTimeval, NULL) == -1)
    {
        printf("Failed to get os time\n");
        s32Ret = -1;
        goto cleanup;
    }

    //保存nalu首byte
    u8NaluBytes = *(pNalBuf+4);
    //設置未發送的Nalu數據指針位置
    pNaluCurr = pNalBuf + 5;
    //設置剩餘的Nalu數據數量
    s32NaluRemain = s32NalBufSize - 5;
	if ((u8NaluBytes&0x1f)==0x7&&0)
	{
		printf("(u8NaluBytes&0x1f)==0x7&&0\r\n");
		pNaluPayload = (pSendBuf + 12);
		if(sps_len>0)
		{	        
	        memcpy(pNaluPayload, sps_tmp, sps_len);
	        if(sendto(hRtp->s32Sock, pSendBuf, sps_len+12, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0)
	        {
	            s32Ret = -1;
	            goto cleanup;
	        }
		}
		if(pps_len>0)
		{	        
	        memcpy(pNaluPayload, pps_tmp, pps_len);
	        if(sendto(hRtp->s32Sock, pSendBuf, pps_len+12, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0)
	        {
	            s32Ret = -1;
	            goto cleanup;
	        }
		}
	}
    //NALU包小於等於最大包長度,直接發送
    if(s32NaluRemain <= MAX_RTP_PKT_LENGTH)
    {
        hRtp->pRtpFixedHdr->u1Marker    = 1;
        hRtp->pRtpFixedHdr->u16SeqNum   = htons(hRtp->u16SeqNum ++);
        hRtp->pNaluHdr                  = (StNaluHdr *)(pSendBuf + 12);
        hRtp->pNaluHdr->u1F             = (u8NaluBytes & 0x80) >> 7;
        hRtp->pNaluHdr->u2Nri           = (u8NaluBytes & 0x60) >> 5;
        hRtp->pNaluHdr->u5Type          = u8NaluBytes & 0x1f;

        pNaluPayload = (pSendBuf + 13);
        memcpy(pNaluPayload, pNaluCurr, s32NaluRemain);

        s32Bytes = s32NaluRemain + 13;
		
		printf("<MAX_RTP_PKT_LENGTH----count=%d\r\n",s32Bytes);
		int i=0;
		printf("send data:0x");
		while(i<50)
		{
			printf("%x ",pSendBuf[i]);
			i++;
		}
		printf("......\r\n");
		
		fflush(stdout);
        if(sendto(hRtp->s32Sock, pSendBuf, s32Bytes, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0)
        {
            s32Ret = -1;
            goto cleanup;
        }
#ifdef SAVE_NALU
        fwrite(pSendBuf, s32Bytes, 1, hRtp->pNaluFile);
#endif
    }
    //NALU包大於最大包長度,分批發送
    else
    {
        //指定fu indicator位置
        hRtp->pFuInd            = (StFuIndicator *)(pSendBuf + 12);
        hRtp->pFuInd->u1F       = (u8NaluBytes & 0x80) >> 7;
        hRtp->pFuInd->u2Nri     = (u8NaluBytes & 0x60) >> 5;
        hRtp->pFuInd->u5Type    = 28;

        //指定fu header位置
        hRtp->pFuHdr            = (StFuHdr *)(pSendBuf + 13);
        hRtp->pFuHdr->u1R       = 0;
        hRtp->pFuHdr->u5Type    = u8NaluBytes & 0x1f;

        //指定payload位置
        pNaluPayload = (pSendBuf + 14);

        //當剩餘Nalu數據多於0時分批發送nalu數據
        while(s32NaluRemain > 0)
        {
            /*配置fixed header*/
            //每個包序號增1
            hRtp->pRtpFixedHdr->u16SeqNum = htons(hRtp->u16SeqNum ++);
            hRtp->pRtpFixedHdr->u1Marker = (s32NaluRemain <= MAX_RTP_PKT_LENGTH) ? 1 : 0;

            /*配置fu header*/
            //最後一批數據則置1
            hRtp->pFuHdr->u1E       = (s32NaluRemain <= MAX_RTP_PKT_LENGTH) ? 1 : 0;
			if(hRtp->pFuHdr->u1E==1)
				printf("***********the last data**************\r\n");
            //第一批數據則置1
            hRtp->pFuHdr->u1S       = (s32NaluRemain == (s32NalBufSize - 5)) ? 1 : 0;
			if(hRtp->pFuHdr->u1S==1)
				printf("***********the first data**************\r\n");


            s32Bytes = (s32NaluRemain < MAX_RTP_PKT_LENGTH) ? s32NaluRemain : MAX_RTP_PKT_LENGTH;


            memcpy(pNaluPayload, pNaluCurr, s32Bytes);

            //發送本批次
            s32Bytes = s32Bytes + 14;
			printf("fu ----count=%d\r\n",s32Bytes);
			int i=0;
			printf("send data:0x");
			while(i<50)
			{
				printf("%x ",pSendBuf[i]);
				i++;
			}
			printf("......\r\n");

			fflush(stdout);
            if(sendto(hRtp->s32Sock, pSendBuf, s32Bytes, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0)
            {
                s32Ret = -1;
                goto cleanup;
            }
#ifdef SAVE_NALU
            fwrite(pSendBuf, s32Bytes, 1, hRtp->pNaluFile);
#endif

            //指向下批數據
            pNaluCurr += MAX_RTP_PKT_LENGTH;
            //計算剩餘的nalu數據長度
            s32NaluRemain -= MAX_RTP_PKT_LENGTH;
        }
    }

cleanup:
    if(pSendBuf)
    {
        free((void *)pSendBuf);
    }
	printf("\n");
	fflush(stdout);

    return s32Ret;
}

注意:
上面的代碼中加入了很多打印信息,是爲了調試看H264封包成RTP數據包的詳細過程。實際使用中,要把上述打印信息屏蔽了,不然打印信息佔用的線程很多時間,導致視頻出現花屏。
在這裏插入圖片描述

H.264數據包和RTP數據包詳細解析

(1)h264原始數據解析

具體h264數據格式建議先查看這篇文章(傳送門),對H264數據結構有所瞭解。
在這裏插入圖片描述
00 00 00 01 67: 0x67&0x1f = 0x07 :SPS
00 00 00 01 68: 0x68&0x1f = 0x08 :PPS
00 00 00 01 06: 0x06&0x1f = 0x06 :SEI信息
00 00 00 01 65: 0x65&0x1f = 0x05: IDR Slice
00 00 00 01 61: 0x61&0x1f = 0x01: P幀

(2)RTP包頭數據結構
下面先來查看相關的結構體

typedef struct _tagStRtpHandle
{
    int                 s32Sock;
    struct sockaddr_in  stServAddr;
    unsigned short      u16SeqNum;
    unsigned long long        u32TimeStampInc;
    unsigned long long        u32TimeStampCurr;
    unsigned long long      u32CurrTime;
    unsigned long long      u32PrevTime;
    unsigned int        u32SSrc;
    StRtpFixedHdr       *pRtpFixedHdr;   //rtp固定頭,12個字節
    StNaluHdr           *pNaluHdr;    //nalu頭,1個字節
    StFuIndicator       *pFuInd;     //fu分包,fu indicator
    StFuHdr             *pFuHdr;   //fu分包,fu header
    EmRtpPayload        emPayload;  //載荷類型
#ifdef SAVE_NALU
    FILE                *pNaluFile;
#endif
} StRtpObj, *HndRtp;

其中, StRtpFixedHdr結構體是RTP的固定頭,共12個字節 (CSRC先忽略),其對應下圖的結構。
在這裏插入圖片描述

typedef struct
{
    /**//* byte 0 */
    unsigned char u4CSrcLen:4;      /**//* expect 0 */
    unsigned char u1Externsion:1;   /**//* expect 1, see RTP_OP below */
    unsigned char u1Padding:1;      /**//* expect 0 */
    unsigned char u2Version:2;      /**//* expect 2 */
    /**//* byte 1 */
    unsigned char u7Payload:7;      /**//* RTP_PAYLOAD_RTSP */
    unsigned char u1Marker:1;       /**//* expect 1 */
    /**//* bytes 2, 3 */
    unsigned short u16SeqNum;
    /**//* bytes 4-7 */
    unsigned long u32TimeStamp;
    /**//* bytes 8-11 */
    unsigned long u32SSrc;          /**//* stream number is used here. */
} StRtpFixedHdr;

RTP包頭各標識說明如下:

  1. V:RTP協議的版本號,佔2位,當前協議版本號爲2

  2. P:填充標誌,佔1位,如果P=1,則在該報文的尾部填充一個或多個額外的八位組,它們不是有效載荷的一部分。

  3. X:擴展標誌,佔1位,如果X=1,則在RTP報頭後跟有一個擴展報頭

  4. CC:CSRC計數器,佔4位,指示CSRC 標識符的個數

  5. M: 標記,佔1位,不同的有效載荷有不同的含義,對於視頻,標記一幀的結束;對於音頻,標記會話的開始。

  6. PT: 有效荷載類型,佔7位,用於說明RTP報文中有效載荷的類型,如GSM音頻、JPEM圖像等,在流媒體中大部分是用來區分音頻流和視頻流的,這樣便於客戶端進行解析。

  7. 序列號:佔16位,用於標識發送者所發送的RTP報文的序列號,每發送一個報文,序列號增1。這個字段當下層的承載協議用UDP的時候,網絡狀況不好的時候可以用來檢查丟包。同時出現網絡抖動的情況可以用來對數據進行重新排序,序列號的初始值是隨機的,同時音頻包和視頻包的sequence是分別記數的。

  8. 時戳(Timestamp):佔32位,必須使用90 kHz 時鐘頻率。時戳反映了該RTP報文的第一個八位組的採樣時刻。接收者使用時戳來計算延遲和延遲抖動,並進行同步控制。

  9. 同步信源(SSRC)標識符:佔32位,用於標識同步信源。該標識符是隨機選擇的,參加同一視頻會議的兩個同步信源不能有相同的SSRC。

  10. 特約信源(CSRC)標識符:每個CSRC標識符佔32位,可以有0~15個。每個CSRC標識了包含在該RTP報文有效載荷中的所有特約信源。

注:基本的RTP說明並不定義任何頭擴展本身,如果遇到X=1,需要特殊處理
在這裏插入圖片描述
如上圖,紅色標記的12個字節就是RTP包頭。
下面來詳細分析下這個12個字節:0x80 60 0 0 18 37 6f 6e 16 1 a8 c0

0x80 是V_P_X_CC
60 是M_PT
00 00 是SequenceNum
18 37 6f 6e 是Timestamp
16 1 a8 c0 是SSRC

把前兩字節換成二進制如下
1000 0000 0110 0000
按順序解釋如下:
10 是V;
0 是P;
0 是X;
0000 是CC;
0 是M;對於視頻,標記一幀的結束,注意後面講解最後一包時,M會設置成1,表示這一幀的結束。
110 0000 是PT;

00 00 是SequenceNum,可以發現自己在自加一,依次增加;

18 37 6f 6e 是Timestamp,同一個rtp分包內的時間戳都是一樣的,不同rtp包的時間戳不一樣;

16 1 a8 c0是SSRC ,代碼裏把SSRC設成了自己的ip地址。c0是192,a8是168,1是1,16是22,故本機地址是192.168.1.22,注意這裏是網絡字節順序。

(2)單個NAL單元包

對於 NALU 的長度小於 MTU 大小的包, 一般採用單一 NAL 單元模式。其封包結構如下圖所示。
在這裏插入圖片描述

也就是說,單包發送時,實際的數據包爲:
12個字節RTP頭 + 1個字節的nalu(F、NRI、type) + 後面的有效數據

NALU 頭由一個字節組成, 它的語法如下:
在這裏插入圖片描述
F:forbidden_zero_bit.1 位,如果有語法衝突,則爲 1。當網絡識別此單元存在比特錯誤時,可將其設爲 1,以便接收方丟掉該單元。

NRI:nal_ref_idc.2 位,用來指示該NALU 的重要性等級。值越大,表示當前NALU越重要。具體大於0 時取何值,沒有具體規定。

Type:5 位,指出NALU 的類型。具體如表所示:
在這裏插入圖片描述
注意:NRI 值爲 7 和 8 的NALU 分別爲序列參數集(sps)和圖像參數集(pps),上面說過。參數集是一組很少改變的,爲大量VCL NALU 提供解碼信息的數據。其中序列參數集作用於一系列連續的編碼圖像,而圖像參數集作用於編碼視頻序列中一個或多個獨立的圖像。如果解碼器沒能正確接收到這兩個參數集,那麼其他NALU 也是無法解碼的。因此它們一般在發送其它 NALU 之前發送,並且使用不同的信道或者更加可靠的傳輸協議(如TCP)進行傳輸,也可以重複傳輸。

(3) RTP分包發送

Fragmentation Units (FUs).

而當 NALU 的長度超過 MTU 時, 就必須對 NALU 單元進行分片封包. 也稱爲 Fragmentation Units (FUs).

   0                   1                   2                   3
   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  | FU indicator  |   FU header   |                               |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
  |                                                               |
  |                         FU payload                            |
  |                                                               |
  |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |                               :...OPTIONAL RTP padding        |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  Figure 14.  RTP payload format for FU-A

The FU indicator octet has the following format:

  +---------------+
  |0|1|2|3|4|5|6|7|
  +-+-+-+-+-+-+-+-+
  |F|NRI|  Type   |
  +---------------+

FU indicator 指示字節的類型域的28,29表示FU-A和FU-B。NRI域的值必須根據分片NAL單元的NRI域的值設置。(此處FU indicator Type是rtp分片類型,和FU header裏的type是不一樣) 見下表:

Type Packet Type name

0 undefined -
1-23 NAL unit Single NAL unit packet per H.264
24 STAP-A Single-time aggregation packet
25 STAP-B Single-time aggregation packet
26 MTAP16 Multi-time aggregation packet
27 MTAP24 Multi-time aggregation packet
28 FU-A Fragmentation unit
29 FU-B Fragmentation unit
30-31 undefined

The FU header has the following format:

  +---------------+
  |0|1|2|3|4|5|6|7|
  +-+-+-+-+-+-+-+-+
  |S|E|R|  Type   |
  +---------------+

S: 1 bit 當設置成1,開始位指示分片NAL單元的開始。當跟隨的FU荷載不是分片NAL單元荷載的開始,開始位設爲0。

E: 1 bit 當設置成1, 結束位指示分片NAL單元的結束,即, 荷載的最後字節也是分片NAL單元的最後一個字節。
當跟隨的FU荷載不是分片NAL單元的最後分片,結束位設置爲0。

R: 1 bit
保留位必須設置爲0,接收者必須忽略該位。

Type: 5 bits
此處的Type就是NALU頭中的Type,取1-23的那個值,表示 NAL單元荷載類型定義

也就是說,RTP分包的數據包結構是:
12個字節RTP頭 + 1個字節的FU indicator(F、NRI、type) + 1個字節的FU header + 後面的有效數據
下面結合本例的代碼進行講解。
在這裏插入圖片描述
可以看到,本例中的RTP一直是分包發送的,沒有單個的NAL單元包發送。
先看第一個分包:
在這裏插入圖片描述
12個字節的RTP頭上面已經解析過了。
7c是FU indicator,二進制爲0111 1100,f爲0(無語法衝突),NRI爲11(表示重要,不可丟棄),type是11100. 即28 ,是FU-A 分包類型。具體FU-A和FU-B分包的詳細區別還沒查到較好的資料,麻煩知道的網友留言告知一下。
87是FU header ,二進制是1000 0111,S是1(表示start,開始的一包),E是0,R 是0,type是111,即7,表示序列參數集(sps)。
後面接着發的是多個字節的有效數據,這些數據是從原始的H264數據包拆分成多包的有效數據,見下圖。
在這裏插入圖片描述
注意觀察第二包的FU header是7 ,二進制是0000 0111,S是0(,E是0,R 是0,type是111,即7,表示序列參數集(sps)。
下面再看下最後一個數據包的數據。
在這裏插入圖片描述
可以看到M_PT由60變成了e0,即0110 0000 變爲1110 0000,即最高位M變成1,標記一幀的結束,表示是最後一包。
FU header從7變成了47 ,二進制由0000 0111變成0100 0111,S是0,E是1(表示end,最後一包),R 是0,type是111,即7,表示序列參數集(sps)。

(4)多幀之間的關聯性

下面看下多幀的數據包圖片。
第一幀:
在這裏插入圖片描述
第二幀:
在這裏插入圖片描述
第三幀:
在這裏插入圖片描述
第四幀:
在這裏插入圖片描述
第五幀、第六幀等等…

00 00 00 01 67:  0x67&0x1f = 0x07 :SPS
00 00 00 01 68:  0x68&0x1f = 0x08 :PPS
00 00 00 01 06:  0x06&0x1f = 0x06 :SEI信息
00 00 00 01 65:  0x65&0x1f = 0x05: IDR Slice
00 00 00 01 61:  0x61&0x1f = 0x01: P幀

從上面可以看出,只有第一幀類型是67(SPS),並且第一幀中包含68(PPS)、0x06 (SEI信息)和0x65( IDR Slice), 後續的幀中只包含0x61(P幀)的數據。

(5)封包源碼詳細剖析

知道了以上的協議知識,下面詳細分析代碼,一些打印信息已去掉了。

static int SendNalu264(HndRtp hRtp, char *pNalBuf, int s32NalBufSize)	
{
    char *pNaluPayload;
    char *pSendBuf;
    int s32Bytes = 0;
    int s32Ret = 0;
    struct timeval stTimeval;
    char *pNaluCurr;
    int s32NaluRemain;
    unsigned char u8NaluBytes;
    pSendBuf = (char *)calloc(MAX_RTP_PKT_LENGTH + 100, sizeof(char));  // 分配一包數據的內存空間,MAX_RTP_PKT_LENGTH是1400,如果單包發送的最大是1413,即1400有效數據+13個字節,分包發送的話是1414,即1400有效數據+14個字節,這裏直接分配1500字節的空間,留了幾十字節的餘量。
    if(NULL == pSendBuf)
    {
        s32Ret = -1;
        goto cleanup;
    }

    hRtp->pRtpFixedHdr = (StRtpFixedHdr *)pSendBuf; //指向分配的內存空間,往這部分內存空間裏放數
    hRtp->pRtpFixedHdr->u7Payload   = H264;  //96,視頻
    hRtp->pRtpFixedHdr->u2Version   = 2;  //版本2
    hRtp->pRtpFixedHdr->u1Marker    = 0;  //M=0,後面是最後一包時會置1
    hRtp->pRtpFixedHdr->u32SSrc     = hRtp->u32SSrc;   //在RtpCreate初始化函數中已經把hRtp->u32SSrc配置爲本機的ip地址了
    //計算時間戳
    hRtp->pRtpFixedHdr->u32TimeStamp = htonl(hRtp->u32TimeStampCurr * (90000 / 1000));
    printf("timestamp:%lld\n",hRtp->u32TimeStampCurr);
    if(gettimeofday(&stTimeval, NULL) == -1)
    {
        printf("Failed to get os time\n");
        s32Ret = -1;
        goto cleanup;
    }

    //保存nalu首byte
    u8NaluBytes = *(pNalBuf+4);  //取出h264原始數據的第5個字節,即nalu頭數據(F、NRI、type)
    //設置未發送的Nalu數據指針位置
    pNaluCurr = pNalBuf + 5;  //指向有效數據包
    //設置剩餘的Nalu數據數量
    s32NaluRemain = s32NalBufSize - 5;
	if ((u8NaluBytes&0x1f)==0x7&&0)  //該分支屏蔽了,不調用
	{
		printf("(u8NaluBytes&0x1f)==0x7&&0\r\n");
		pNaluPayload = (pSendBuf + 12);
		if(sps_len>0)
		{	        
	        memcpy(pNaluPayload, sps_tmp, sps_len);
	        if(sendto(hRtp->s32Sock, pSendBuf, sps_len+12, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0)
	        {
	            s32Ret = -1;
	            goto cleanup;
	        }
		}
		if(pps_len>0)
		{	        
	        memcpy(pNaluPayload, pps_tmp, pps_len);
	        if(sendto(hRtp->s32Sock, pSendBuf, pps_len+12, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0)
	        {
	            s32Ret = -1;
	            goto cleanup;
	        }
		}
	}
    //NALU包小於等於最大包長度,直接發送
    if(s32NaluRemain <= MAX_RTP_PKT_LENGTH)
    {
        //12個字節RTP頭   +     1個字節的nalu(F、NRI、type)    +     後面的有效數據
        
        hRtp->pRtpFixedHdr->u1Marker    = 1;  //由於是單包,所以這一包發送完了就沒下一包了,M置1
        hRtp->pRtpFixedHdr->u16SeqNum   = htons(hRtp->u16SeqNum ++);  //序列號自加1
        hRtp->pNaluHdr                  = (StNaluHdr *)(pSendBuf + 12);  //RTP包數據的第13個字節地址
        hRtp->pNaluHdr->u1F             = (u8NaluBytes & 0x80) >> 7;  //F
        hRtp->pNaluHdr->u2Nri           = (u8NaluBytes & 0x60) >> 5;  //NRI
        hRtp->pNaluHdr->u5Type          = u8NaluBytes & 0x1f;   //type

        pNaluPayload = (pSendBuf + 13);
        memcpy(pNaluPayload, pNaluCurr, s32NaluRemain);  //把後面有效數據包拷貝過來
        s32Bytes = s32NaluRemain + 13;  //12個字節RTP頭   +     1個字節的nalu(F、NRI、type)    +     後面的有效數據
        //以udp方式把pSendBuf指向的RTP數據包發出去
        if(sendto(hRtp->s32Sock, pSendBuf, s32Bytes, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0)
        {
            s32Ret = -1;
            goto cleanup;
        }
#ifdef SAVE_NALU
        fwrite(pSendBuf, s32Bytes, 1, hRtp->pNaluFile);
#endif
    }
    //NALU包大於最大包長度,分批發送
    else
    {
//12個字節RTP頭   +     1個字節的FU indicator(F、NRI、type) +  1個字節的FU  header +     後面的有效數據

        //指定fu indicator位置
        hRtp->pFuInd            = (StFuIndicator *)(pSendBuf + 12); //RTP包數據的第13個字節地址,fu indicator位置
        hRtp->pFuInd->u1F       = (u8NaluBytes & 0x80) >> 7;//F
        hRtp->pFuInd->u2Nri     = (u8NaluBytes & 0x60) >> 5;//NRI
        hRtp->pFuInd->u5Type    = 28;     //FU-A      Fragmentation unit  

        //指定fu header位置
        hRtp->pFuHdr            = (StFuHdr *)(pSendBuf + 13);  ////RTP包數據的第14個字節地址,即fu header位置
        hRtp->pFuHdr->u1R       = 0;  //R=0,後面會配置S  、 E
        hRtp->pFuHdr->u5Type    = u8NaluBytes & 0x1f;  //與Nalu頭的type是一樣的

        //指定payload位置
        pNaluPayload = (pSendBuf + 14);  //有效數據包

        //當剩餘Nalu數據多於0時分批發送nalu數據
        while(s32NaluRemain > 0)
        {
            /*配置fixed header*/
            
            hRtp->pRtpFixedHdr->u16SeqNum = htons(hRtp->u16SeqNum ++);//每個包序號增1
            hRtp->pRtpFixedHdr->u1Marker = (s32NaluRemain <= MAX_RTP_PKT_LENGTH) ? 1 : 0;  //如果剩餘字節小於一包的最大值,說明是最後一包,M置1,否則不是最後一包,置0

            /*配置fu header*/
            //最後一批數據則置1
            hRtp->pFuHdr->u1E       = (s32NaluRemain <= MAX_RTP_PKT_LENGTH) ? 1 : 0;//如果剩餘字節小於一包的最大值,說明是最後一包,E置1,否則不是最後一包,置0
			
            s32Bytes = (s32NaluRemain < MAX_RTP_PKT_LENGTH) ? s32NaluRemain : MAX_RTP_PKT_LENGTH; // 確定一包的字節數,剩餘字節小於一包的最大值,說明是最後一包,即一包的數據爲剩餘字節,否則是一包的最大值


            memcpy(pNaluPayload, pNaluCurr, s32Bytes);

            //發送本批次
            s32Bytes = s32Bytes + 14;  //有效數據加上前面的14個字節
            if(sendto(hRtp->s32Sock, pSendBuf, s32Bytes, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0)
            {
                s32Ret = -1;
                goto cleanup;
            }
#ifdef SAVE_NALU
            fwrite(pSendBuf, s32Bytes, 1, hRtp->pNaluFile);
#endif

            //指向下批數據
            pNaluCurr += MAX_RTP_PKT_LENGTH;  //發完一包,指針移動一包的字節數
            //計算剩餘的nalu數據長度
            s32NaluRemain -= MAX_RTP_PKT_LENGTH;//發完一包,剩餘字節減去一包字節數
        }
    }

cleanup:
    if(pSendBuf)
    {
        free((void *)pSendBuf);
    }
	printf("\n");
	fflush(stdout);

    return s32Ret;
}

自此,H.264數據包 封包爲 RTP數據包的協議格式和源代碼終於分析完了。如有錯誤,還請指出。
累死了,碼字不易,先休息會,後面繼續分析其他部分,歡迎關注!

參考:
http://www.iosxxx.com/blog/2017-08-09-從零瞭解H264結構.html
https://blog.csdn.net/chen495810242/article/details/39207305
https://www.cnblogs.com/lidabo/p/4582040.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章