前言
由於調試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包頭各標識說明如下:
-
V:RTP協議的版本號,佔2位,當前協議版本號爲2
-
P:填充標誌,佔1位,如果P=1,則在該報文的尾部填充一個或多個額外的八位組,它們不是有效載荷的一部分。
-
X:擴展標誌,佔1位,如果X=1,則在RTP報頭後跟有一個擴展報頭
-
CC:CSRC計數器,佔4位,指示CSRC 標識符的個數
-
M: 標記,佔1位,不同的有效載荷有不同的含義,對於視頻,標記一幀的結束;對於音頻,標記會話的開始。
-
PT: 有效荷載類型,佔7位,用於說明RTP報文中有效載荷的類型,如GSM音頻、JPEM圖像等,在流媒體中大部分是用來區分音頻流和視頻流的,這樣便於客戶端進行解析。
-
序列號:佔16位,用於標識發送者所發送的RTP報文的序列號,每發送一個報文,序列號增1。這個字段當下層的承載協議用UDP的時候,網絡狀況不好的時候可以用來檢查丟包。同時出現網絡抖動的情況可以用來對數據進行重新排序,序列號的初始值是隨機的,同時音頻包和視頻包的sequence是分別記數的。
-
時戳(Timestamp):佔32位,必須使用90 kHz 時鐘頻率。時戳反映了該RTP報文的第一個八位組的採樣時刻。接收者使用時戳來計算延遲和延遲抖動,並進行同步控制。
-
同步信源(SSRC)標識符:佔32位,用於標識同步信源。該標識符是隨機選擇的,參加同一視頻會議的兩個同步信源不能有相同的SSRC。
-
特約信源(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