(1)標示位(M ):1 位,該標示位的含義一般由具體的媒體應用框架(profile )定義, 目的在於標記處RTP 流中的重要事件。
(2)載荷類型(PT):7 位,用來指出RTP負載的具體格式。在RFC3551中,對常用的音視頻格式的RTP 傳輸載荷類型做了默認的取值規定,例如,類型2 表明該RTP數據包中承載的是用ITU G.721 算法編碼的語音數據,採用頻率爲 8000HZ,並且採用單聲道。
(3)序號:16 位,每發送一個 RTP 數據包,序號加 1。接受者可以用它來檢測分組丟失和恢復分組順序。
(4)時間戳:32 位,時間戳表示了 RTP 數據分組中第一個字節的採樣時間,反映出各RTP 包相對於時間戳初始值的偏差。對於RTP 發送端而言,採樣時間必須來源於一個線性單調遞增的時鐘。
從 RTP 數據包的格式不難看出,它包含了傳輸媒體的類型、格式、序列號、時間戳以及是否有附加數據等信息。這些都爲實時的流媒體傳輸提供了相應的基礎。而 傳輸控制協議RTCP爲 RTP傳輸提供了擁塞控制和流控制,它的具體包結構和各字段的含義可參考RFC3550,此處不再贅述。
H.264 的基本流由一系列NALU (Network Abstraction Layer Unit )組成,不同的NALU數據量各不相同。H.264 草案指出[2],當數據流是儲存在介質上時,在每個NALU 前添加起始碼:0x000001,用來指示一個 NALU的起始和終止位置。在這樣的機制下,*在碼流中檢測起始碼,作爲一個NALU得起始標識,當檢測到下一個起始碼時,當前NALU結束。每個 NALU單元由一個字節的 NALU頭(NALU Header)和若干個字節的載荷數據(RBSP)組成。其中NALU 頭的格式如圖2 所示:
F:forbidden_zero_bit.1 位,如果有語法衝突,則爲 1。當網絡識別此單元存在比特錯誤時,可將其設爲 1,以便接收方丟掉該單元。
NRI:nal_ref_idc.2 位,用來指示該NALU 的重要性等級。值越大,表示當前NALU越重要。具體大於0 時取何值,沒有具體規定。
需要特別指出的是,NRI 值爲 7 和 8 的NALU 分別爲序列參數集(sps)和圖像參數集(pps)。參數集是一組很少改變的,爲大量VCL NALU 提供解碼信息的數據。其中序列參數集作用於一系列連續的編碼圖像,而圖像參數集作用於編碼視頻序列中一個或多個獨立的圖像。如果*沒能正確接收到這兩個參 數集,那麼其他NALU 也是無法解碼的。因此它們一般在發送其它 NALU 之前發送,並且使用不同的信道或者更加可靠的傳輸協議(如TCP)進行傳輸,也可以重複傳輸。
前 面分別討論了RTP 協議及H.264基本流的結構,那麼如何使用RTP協議來傳輸H.264視頻了?一個有效的辦法就是從H.264視頻中剝離出每個NALU,在每個 NALU前添加相應的RTP包頭,然後將包含RTP 包頭和NALU 的數據包發送出去。下面就從RTP包頭和NALU兩方面分別闡述。
完整的 RTP 固定包頭的格式在前面圖 1 中已經指出,根據RFC3984[3],這裏詳細給出各個位的具體設置。
V:版本號,2 位。根據RFC3984,目前使用的RTP 版本號應設爲0x10。
P:填充位,1 位。當前不使用特殊的加密算法,因此該位設爲 0。
X:擴展位,1 位。當前固定頭後面不跟隨頭擴展,因此該位也爲 0。
CC:CSRC 計數,4 位。表示跟在 RTP 固定包頭後面CSRC 的數目,對於本文所要實現的基本的流媒體服務器來說,沒有用到混合器,該位也設爲 0x0。
M:標示位,1 位。如果當前 NALU爲一個接入單元最後的那個NALU,那麼將M位置 1;或者當前RTP 數據包爲一個NALU 的最後的那個分片時(NALU 的分片在後面講述),M位置 1。其餘情況下M 位保持爲 0。
PT:載荷類型,7 位。對於H.264 視頻格式,當前並沒有規定一個默認的PT 值。因此選用大於 95 的值可以。此處設爲0x60(十進制96)。
SQ:序號,16 位。序號的起始值爲隨機值,此處設爲 0,每發送一個RTP 數據包,序號值加 1。
TS:時間戳,32 位。同序號一樣,時間戳的起始值也爲隨機值,此處設爲0。根據RFC3984, 與時間戳相應的時鐘頻率必須爲90000HZ。
SSRC:同步源標示,32 位。SSRC應該被隨機生成,以使在同一個RTP會話期中沒有任何兩個同步源具有相同的SSRC 識別符。此處僅有一個同步源,因此將其設爲0x12345678。
對於每一個NALU,根據其包含的數據量的不同,其大小也有差異。在IP網絡中,當要傳輸的IP 報文大小超過最大傳輸單元MTU(Maximum Transmission Unit )時就會產生IP分片情況。在以太網環境中可傳輸的最大 IP 報文(MTU)的大小爲 1500 字節。如果發送的IP數據包大於MTU,數據包就會被拆開來傳送,這樣就會產生很多數據包碎片,增加丟包率,降低網絡速度。對於視頻傳輸而言,若RTP 包大於MTU 而由底層協議任意拆包,可能會導致接收端播放器的延時播放甚至無法正常播放。因此對於大於MTU 的NALU 單元,必須進行拆包處理。
(2)Aggregation Packet:在一個RTP 包中封裝多個NALU,對於較小的NALU 可以採用這種打包方案,從而提高傳輸效率。
(3)Fragmentation Unit:一個NALU 封裝在多個RTP包中,在本文中,對於大於1400字節的NALU 便採用這種方案進行拆包處理。
當一個NALU小於1400字節的時候,採用一個單RTP包發送
if(n->len<=1400)
{
//設置rtp M 位;
rtp_hdr->marker=1;
rtp_hdr->seq_no = htons(seq_num ++); //序列號,每發送一個RTP包增1
//設置NALU HEADER,並將這個HEADER填入sendbuf[12]
nalu_hdr =(NALU_HEADER*)&sendbuf[12]; //將sendbuf[12]的地址賦給nalu_hdr,之後對nalu_hdr的寫入就將寫入sendbuf中;
nalu_hdr->F=n->forbidden_bit;
nalu_hdr->NRI=n->nal_reference_idc>>5;//有效數據在n->nal_reference_idc的第6,7位,需要右移5位才能將其值賦給nalu_hdr->NRI。
nalu_hdr->TYPE=n->nal_unit_type;
nalu_payload=&sendbuf[13];//同理將sendbuf[13]賦給nalu_payload
memcpy(nalu_payload,n->buf+1,n->len-1);//去掉nalu頭的nalu剩餘內容寫入sendbuf[13]開始的字符串。
ts_current=ts_current+timestamp_increse;
rtp_hdr->timestamp=htonl(ts_current);
bytes=n->len + 12 ; //獲得sendbuf的長度,爲nalu的長度(包含NALU頭但除去起始前綴)加上rtp_header的固定長度12字節
send( socket1, sendbuf, bytes, 0 );//發送rtp包
// Sleep(100);
}
else if(n->len>1400)
{
//得到該nalu需要用多少長度爲1400字節的RTP包來發送
int k=0,l=0;
k=n->len/1400;//需要k個1400字節的RTP包
l=n->len%1400;//最後一個RTP包的需要裝載的字節數
int t=0;//用於指示當前發送的是第幾個分片RTP包
ts_current=ts_current+timestamp_increse;
rtp_hdr->timestamp=htonl(ts_current);
while(t<=k)
{
rtp_hdr->seq_no = htons(seq_num ++); //序列號,每發送一個RTP包增1
if(!t)//發送一個需要分片的NALU的第一個分片,置FU HEADER的S位
{
//設置rtp M 位;
rtp_hdr->marker=0;
//設置FU INDICATOR,並將這個HEADER填入sendbuf[12]
fu_ind =(FU_INDICATOR*)&sendbuf[12]; //將sendbuf[12]的地址賦給fu_ind,之後對fu_ind的寫入就將寫入sendbuf中;
fu_ind->F=n->forbidden_bit;
fu_ind->NRI=n->nal_reference_idc>>5;
fu_ind->TYPE=28;
//設置FU HEADER,並將這個HEADER填入sendbuf[13]
fu_hdr =(FU_HEADER*)&sendbuf[13];
fu_hdr->E=0;
fu_hdr->R=0;
fu_hdr->S=1;
fu_hdr->TYPE=n->nal_unit_type;
nalu_payload=&sendbuf[14];//同理將sendbuf[14]賦給nalu_payload
memcpy(nalu_payload,n->buf+1,1400);//去掉NALU頭
bytes=1400+14; //獲得sendbuf的長度,爲nalu的長度(除去起始前綴和NALU頭)加上rtp_header,fu_ind,fu_hdr的固定長度14字節
send( socket1, sendbuf, bytes, 0 );//發送rtp包
t++;
}
//發送一個需要分片的NALU的非第一個分片,清零FU HEADER的S位,如果該分片是該NALU的最後一個分片,置FU HEADER的E位
else if(k==t)//發送的是最後一個分片,注意最後一個分片的長度可能超過1400字節(當l>1386時)。
{
//設置rtp M 位;當前傳輸的是最後一個分片時該位置1
rtp_hdr->marker=1;
//設置FU INDICATOR,並將這個HEADER填入sendbuf[12]
fu_ind =(FU_INDICATOR*)&sendbuf[12]; //將sendbuf[12]的地址賦給fu_ind,之後對fu_ind的寫入就將寫入sendbuf中;
fu_ind->F=n->forbidden_bit;
fu_ind->NRI=n->nal_reference_idc>>5;
fu_ind->TYPE=28;
//設置FU HEADER,並將這個HEADER填入sendbuf[13]
fu_hdr =(FU_HEADER*)&sendbuf[13];
fu_hdr->R=0;
fu_hdr->S=0;
fu_hdr->TYPE=n->nal_unit_type;
fu_hdr->E=1;
nalu_payload=&sendbuf[14];//同理將sendbuf[14]的地址賦給nalu_payload
memcpy(nalu_payload,n->buf+t*1400+1,l-1);//將nalu最後剩餘的l-1(去掉了一個字節的NALU頭)字節內容寫入sendbuf[14]開始的字符串。
bytes=l-1+14; //獲得sendbuf的長度,爲剩餘nalu的長度l-1加上rtp_header,FU_INDICATOR,FU_HEADER三個包頭共14字節
send( socket1, sendbuf, bytes, 0 );//發送rtp包
t++;
// Sleep(100);
}
else if(t<k&&0!=t)
{
//設置rtp M 位;
rtp_hdr->marker=0;
//設置FU INDICATOR,並將這個HEADER填入sendbuf[12]
fu_ind =(FU_INDICATOR*)&sendbuf[12]; //將sendbuf[12]的地址賦給fu_ind,之後對fu_ind的寫入就將寫入sendbuf中;
fu_ind->F=n->forbidden_bit;
fu_ind->NRI=n->nal_reference_idc>>5;
fu_ind->TYPE=28;
//設置FU HEADER,並將這個HEADER填入sendbuf[13]
fu_hdr =(FU_HEADER*)&sendbuf[13];
//fu_hdr->E=0;
fu_hdr->R=0;
fu_hdr->S=0;
fu_hdr->E=0;
fu_hdr->TYPE=n->nal_unit_type;
nalu_payload=&sendbuf[14];//同理將sendbuf[14]的地址賦給nalu_payload
memcpy(nalu_payload,n->buf+t*1400+1,1400);//去掉起始前綴的nalu剩餘內容寫入sendbuf[14]開始的字符串。
bytes=1400+14; //獲得sendbuf的長度,爲nalu的長度(除去原NALU頭)加上rtp_header,fu_ind,fu_hdr的固定長度14字節
send( socket1, sendbuf, bytes, 0 );//發送rtp包
t++;
}
}