從攝像頭獲取的視頻數(shù)據(jù)叹螟,經(jīng)過編碼后(當(dāng)然魄宏,也可以不編碼秸侣,如果你覺得也很ok的話),可以視頻錄制宠互,同時(shí)如果需要味榛,當(dāng)然也可以視頻遠(yuǎn)程傳輸咯,而實(shí)時(shí)傳輸協(xié)議(Real-time Transport Protocol予跌,RTP)是在Internet上處理多媒體數(shù)據(jù)流的一種網(wǎng)絡(luò)協(xié)議搏色,利用它能夠在一對(duì)一(unicast,單播)或者一對(duì)多(multicast券册,多播)的網(wǎng)絡(luò)環(huán)境中實(shí)現(xiàn)傳流媒體數(shù)據(jù)的實(shí)時(shí)傳輸(不需要下載完畢后才能看視頻)频轿。RTP通常使用UDP來進(jìn)行多媒體數(shù)據(jù)的傳輸,但如果需要的話可以使用TCP等其它協(xié)議烁焙,**整個(gè)RTP協(xié)議由兩個(gè)密切相關(guān)的部分組成:RTP數(shù)據(jù)協(xié)議和RTCP控制協(xié)議航邢。
RTP數(shù)據(jù)協(xié)議**負(fù)責(zé)對(duì)流媒體數(shù)據(jù)進(jìn)行封包并實(shí)現(xiàn)媒體流的實(shí)時(shí)傳輸,每一個(gè)RTP數(shù)據(jù)報(bào)都由頭部(Header)和負(fù)載(Payload)兩個(gè)部分組成考阱,其中頭部前12個(gè)字節(jié)的含義是固定的翠忠,而負(fù)載則可以是音頻或者視頻數(shù)據(jù)。
RTCP 控制協(xié)議需要與RTP數(shù)據(jù)協(xié)議一起配合使用乞榨,當(dāng)應(yīng)用程序啟動(dòng)一個(gè)RTP會(huì)話時(shí)將同時(shí)占用兩個(gè)端口秽之,分別供RTP和RTCP使用。RTP本身并不能為按序傳輸數(shù)據(jù)包提供可靠的保證吃既,也不提供流量控制和擁塞控制考榨,這些都由RTCP來負(fù)責(zé)完成。通常RTCP會(huì)采用與RTP相同的分發(fā)機(jī)制鹦倚,向會(huì)話中的所有成員周期性地發(fā)送控制信息河质,應(yīng)用程序通過接收這些數(shù)據(jù),從中獲取會(huì)話參與者的相關(guān)資料震叙,以及網(wǎng)絡(luò)狀況掀鹅、分組丟失概率等反饋信息,從而能 夠?qū)Ψ?wù)質(zhì)量進(jìn)行控制或者對(duì)網(wǎng)絡(luò)狀況進(jìn)行診斷媒楼。
實(shí)時(shí)流協(xié)議(RealTime Streaming Protocol乐尊,RTSP),它的意義在于使得實(shí)時(shí)流媒體數(shù)據(jù)的受控和點(diǎn)播變得可能划址。總的說來扔嵌,RTSP是一個(gè)流媒體表示協(xié)議限府, 主要用來控制具有實(shí)時(shí)特性的數(shù)據(jù)發(fā)送,但它本身并不傳輸數(shù)據(jù)痢缎,而是必須依賴于下層傳輸協(xié)議所提供的某些服務(wù)胁勺。RTSP 可以對(duì)流媒體提供諸如播放、暫停独旷、快進(jìn)**等操作署穗,它負(fù)責(zé)定義具體的控制消息、操作方法嵌洼、狀態(tài)碼等蛇捌,此外還描述了與RTP間的交互操作。
The RTP header has the following format:
RTP header格式組成咱台,見下圖:
各個(gè)字段代表含義如下:
V:版本號(hào),一般為2;
P:填充字段標(biāo)識(shí)俭驮;
X:擴(kuò)展頭標(biāo)識(shí)回溺;
CC:CSRC計(jì)數(shù),4比特
M:標(biāo)志 1bit混萝,在傳輸h264時(shí)表示h264 nalu的最后一包
PT:負(fù)載類型 7 bits, H264類型為96,荷載類型的賦值或者通過profile或者通過動(dòng)態(tài)方式
SN:序列號(hào)16 bits
Timestamp:時(shí)間戳32bits遗遵,如果為視頻的話,應(yīng)該設(shè)置為1/9000,音頻為1/8000逸嘀,如果NAL單元沒有
他自己的時(shí)間屬性(即,parameter set and SEI NAL units)车要,RTP時(shí)戳設(shè)置成訪問單元主 編碼圖像的RTP時(shí)戳。
SSRC:32bits崭倘,用以識(shí)別同步源翼岁。
CSRC列表:0到15項(xiàng),每項(xiàng)32比特司光,CSRC列表識(shí)別在此包中負(fù)載的所有貢獻(xiàn)源琅坡。識(shí)別符的數(shù)目在CC域中
給定。若有貢獻(xiàn)源多于15個(gè)残家,僅識(shí)別15個(gè)榆俺。CSRC識(shí)別符由混合器插入,并列出所有貢獻(xiàn)源的 SSRC識(shí)別符坞淮。例如語(yǔ)音包茴晋,混合產(chǎn)生新包的所有源的SSRC標(biāo)識(shí)符都被列出,這樣可以在接收端正確指示參與者回窘。
contributing source (CSRC) identifiers 的組成:如下:
[StartCode] [NALU Header] [NALU Payload]
關(guān)于NALU使用RTP包進(jìn)行發(fā)送可能的類型有:
1. 單一 NAL 單元模式(NALU的長(zhǎng)度小于 MTU 大小的包)
即一個(gè) RTP 包僅由一個(gè)完整的 NALU 組成诺擅。這種情況下 RTP NAL 頭類型字段和原始的H.264的NALU頭類型字段是一樣的。
對(duì)于 NALU的長(zhǎng)度小于 MTU 大小的包毫玖,一般采用單一 NAL 單元模式掀虎。對(duì)于一個(gè)原始的H.264 NALU 單元常由 [StartCode] [NALU Header] [NALU Payload] 三部分組成凌盯,其中 Start Code 用于標(biāo)示這是一個(gè)NALU單元的開始,必須是"00 00 00 01" 或"00 00 01"烹玉, NALU 頭僅一個(gè)字節(jié)驰怎,其后都是 NALU 單元內(nèi)容。打包時(shí)去除 "00 00 01" 或"00 00 00 01" 的開始碼二打,把其他數(shù)據(jù)封包的 RTP 包即可县忌,有如下例子:
[00 0000 01 67 42 A0 1E 23 56 0E 2F ... ]
封裝成 RTP 包將如下:
[ RTPHeader ] [ 67 42 A0 1E 23 56 0E 2F ]
(在這里要說明的是,如果客戶端是通用的播放器继效,比如VLC或者JM的話需要將前導(dǎo)碼去掉症杏,但是如果使用的是ffmpeg在客戶端解碼的話,發(fā)送前不需要去掉前導(dǎo)碼瑞信,去掉之后可能會(huì)導(dǎo)致ffmpeg解碼錯(cuò)誤)厉颤。
H.264Payload 格式定義了三種不同的基本的負(fù)載(Payload)結(jié)構(gòu),接收端可能通過RTP Payload的第一個(gè)字節(jié)來識(shí)別它們凡简。這一個(gè)字節(jié)類似NALU 頭的格式逼友,而這個(gè)頭結(jié)構(gòu)的NAL 單元類型字段則指出了代表的哪一種結(jié)構(gòu)帜乞,這個(gè)字節(jié)的結(jié)構(gòu)如下:
F 1比特
NRI 2比特
Type 5比特
可以看出它和H.264 的NALU 頭結(jié)構(gòu)是一樣的匀谣。
字段Type:這個(gè)RTP payload 中 NAL 單元的類型。 這個(gè)字段和 H.264 中類型字段的區(qū)別是,當(dāng)type的值為24-31表示這是一個(gè)特別格式的 NAL 單元,而H.264中更米,只取1-23是有效的值。
2. 分片封包模式(NALU的長(zhǎng)度大于 MTU 大小的包)
用于把一個(gè) NALU單元封裝成多個(gè) RTP 包。存在兩種類型 FU-A 和 FU-B。類型值分別是 28 和 29丁眼。 而當(dāng) NALU 的長(zhǎng)度超過 MTU 時(shí)嵌施,就必須對(duì) NALU 單元進(jìn)行分片封包。 也稱為Fragmentation Units(FUs)吃靠。 將NALU拆分成小于MTU的數(shù)據(jù)包進(jìn)行發(fā)送硫眨,如果使用的是VLC等網(wǎng)絡(luò)播放器的話巢块,需要設(shè)置FU header礁阁,如下圖所示:
如果使用的是ffmpeg自行進(jìn)行數(shù)據(jù)包接收與解碼,則完全不必寫FU header族奢。其實(shí)在后面的實(shí)際操作中會(huì)發(fā)現(xiàn)姥闭,SPS、PPS都是非常小越走,不到一百個(gè)字節(jié)棚品,都是單個(gè)的NAl進(jìn)行打包發(fā)送,而I幀一般都比較大廊敌,會(huì)采用分包發(fā)送铜跑,一般也是FU-A方式分片,其中MTU一般是1500個(gè)字節(jié)骡澈。FFmpeg中都有現(xiàn)成的源程序可以參考的锅纺。
對(duì)于H264的I幀、P幀等主要是FU(分片)發(fā)送肋殴,那么FU到底是怎樣一個(gè)過程呢囤锉。
相同NAL單元的分片必須使用遞增的RTP序號(hào)連續(xù)順序發(fā)送(第一和最后分片之間沒有其他的RTP包)坦弟。相似, NAL單元必須按照RTP順序號(hào)的順序裝配。FUs不可以嵌套官地。即 一個(gè)FU 不可以包含另一個(gè)FU酿傍。運(yùn)送FU的RTP時(shí)戳被設(shè)置成分片NALU的NALU的時(shí)刻。
FU-A的RTP荷載格式:
NRI: 2 bits, 00值指示NAL單元的內(nèi)容不用于重建影響圖像的幀間圖像預(yù)測(cè).這樣的NAL單元可以被丟棄而不用冒影響圖像完整性的風(fēng)險(xiǎn)区丑。大于00的值指示NAL單元的解碼要求維護(hù)引用圖像的完整性拧粪。
注意:任何非零的NRI在H.264 解碼器的處理是相同的。因此,接收者在傳送NAL單元給解碼器時(shí)不必操作NRI的值沧侥。NRI值必須根據(jù)分片NAL單元的NRI值設(shè)置可霎。H.264編碼器必須根據(jù)H.264規(guī)范設(shè)置NRI值。
當(dāng)nal_unit_type 范圍的是1到12宴杀。特別是癣朗,H.264規(guī)范要求對(duì)于nal_unit_type為6,9旺罢,10旷余,11,12的NAL單元的NRI的值應(yīng)該為0扁达。
對(duì)于nal_unit_type等于7正卧,8 (指示順序參數(shù)集或圖像參數(shù)集)的NAL單元,H.264編碼器應(yīng)該設(shè)置NRI為11 (二進(jìn)制格式)。
對(duì)于nal_unit_type等于5的主編碼圖像的編碼片NAL單元(指示編碼片屬于一個(gè)IDR圖像), H.264編碼器應(yīng)設(shè)置NRI為11跪解。
S: (1 bit)
當(dāng)設(shè)置成1炉旷,開始位指示分片NAL單元的開始。當(dāng)跟隨的FU荷載不是分片NAL單元荷載的開始叉讥,開始位設(shè)為0窘行。
E: (1 bit)
當(dāng)設(shè)置成1,結(jié)束位指示分片NAL單元的結(jié)束图仓,即荷載的最后字節(jié)也是分片NAL單元的最后一個(gè)字節(jié)罐盔。
當(dāng)跟隨的FU荷載不是分片NAL單元的最后分片,結(jié)束位設(shè)置為0。
R: (1 bit)
保留位必須設(shè)置為0救崔,接收者必須忽略該位惶看。
Type: (5 bit)
NAL單元荷載類型定義。
FU payload : 分片單元荷載六孵。
拷貝海思對(duì)應(yīng)代碼如下:
HI_S32 VENC_Sent(char *buffer,int buflen)
{
HI_S32 i;
//--------------------------------------------------------
int is=0;
int nChanNum=0;
//printf("s");
for(is=0;is<MAX_RTSP_CLIENT;is++)
{printf("g_rtspClients[is].status:%d\n",g_rtspClients[is].status);
if(g_rtspClients[is].status!=RTSP_SENDING)
{
continue;
}
int heart = g_rtspClients[is].seqnum % 1000;
printf("g_rtspClients[is].seqnum:%d\n",g_rtspClients[is].seqnum);
if(heart==0 && g_rtspClients[is].seqnum!=0)
{
char buf[1024];
memset(buf,0,1024);
char *pTemp = buf;
pTemp += sprintf(pTemp,"RTSP/1.0 200 OK\r\nCSeq: %s\r\nPublic: %s\r\n\r\n",
0,"OPTIONS,DESCRIBE,SETUP,PLAY,PAUSE,TEARDOWN");
printf("heart 0\n");
int reg = send(g_rtspClients[is].socket, buf,strlen(buf),0);
if(reg <= 0)
{
printf("RTSP:Send Error---- %d\n",reg);
g_rtspClients[is].status = RTSP_IDLE;
g_rtspClients[is].seqnum = 0;
g_rtspClients[is].tsvid = 0;
g_rtspClients[is].tsaud = 0;
close(g_rtspClients[is].socket);
continue;
}
else
{
printf("Heart:%d\n",reg);
}
}
char* nalu_payload;
int nAvFrmLen = 0;
int nIsIFrm = 0;
int nNaluType = 0;
char sendbuf[320*1024+32];
//HI_S32 intdelta = -1000,tmptime;
//HI_S32 intdelta = 0,tmptime;
//char sendbuf[64*1024+32];
nChanNum = g_rtspClients[is].reqchn;
printf("g_rtspClients[is].reqchn:%d\n",g_rtspClients[is].reqchn);
//if(nChanNum<0 || nChanNum>=MAX_CHAN )//
//{
// continue;
//}
nAvFrmLen = buflen;
printf("nAvFrmLen:%d\n",nAvFrmLen);
//nAvFrmLen = vStreamInfo.dwSize ;//Streamlen
struct sockaddr_in server;
server.sin_family=AF_INET;
printf("g_rtspClients[is].rtpport[0]:%d\n",g_rtspClients[is].rtpport[0]);
printf("g_rtspClients[is].IP:%s\n",g_rtspClients[is].IP);
server.sin_port=htons(g_rtspClients[is].rtpport[0]);
server.sin_addr.s_addr=inet_addr(g_rtspClients[is].IP);
//printf("server.sin_port:%s\n",server.sin_port);
//printf("server.sin_addr.s_addr:%s\n",server.sin_addr.s_addr);
int bytes=0;
unsigned int timestamp_increse=0;
//timeing in = out,15fps in,so same f out
//if(VIDEO_ENCODING_MODE_PAL == gs_enNorm)g_nframerate = 15;
//else if(VIDEO_ENCODING_MODE_NTSC == gs_enNorm)g_nframerate = 30;
if(VIDEO_ENCODING_MODE_PAL == gs_enNorm)g_nframerate = 16;
else if(VIDEO_ENCODING_MODE_NTSC == gs_enNorm)g_nframerate = 32;
//if(VIDEO_ENCODING_MODE_PAL == gs_enNorm)timestamp_increse=5625;//90000/16
//else if(VIDEO_ENCODING_MODE_NTSC == gs_enNorm)timestamp_increse=2813;//90000/32
//timestamp_increse=(unsigned int)(90000.0 / g_nframerate);
//sendto(udpfd, buffer, nAvFrmLen, 0, (struct sockaddr *)&server,sizeof(server));
struct timeval tv;
gettimeofday(&tv , NULL);//獲得系統(tǒng)當(dāng)前的時(shí)間
timestamp_increse = (uint)(90000.0 / 15(1000.0 / ((tv.tv_sec - tv_pre.tv_sec) * 1000.0 + (tv.tv_usec - tv_pre.tv_usec) / 1000.0))); //時(shí)間戳增量的計(jì)算碳竟;tv結(jié)構(gòu)體的tv_sec(秒)成員乘以1000加上tv_usec(微秒)除以1000,單位統(tǒng)一化成毫秒
memcpy(&tv_pre, &tv, sizeof(tv_pre));
rtp_hdr =(RTP_FIXED_HEADER*)&sendbuf[0]; 將sendbuf[0]d的地址指向rtp_hdr,填充rtp_hdr結(jié)構(gòu)體的內(nèi)容是通過往sendbuf中寫
rtp_hdr->payload = RTP_H264; //負(fù)載類型
rtp_hdr->version = 2; //版本號(hào)
rtp_hdr->marker = 0; //最后一個(gè)NALU時(shí)狸臣,該值設(shè)置成1莹桅,其他都設(shè)置成0。
rtp_hdr->ssrc = htonl(10); //
// 當(dāng)一個(gè)NALU小于1400字節(jié)的時(shí)候,采用一個(gè)單RTP包發(fā)送
if(nAvFrmLen<=nalu_sent_len)
{
//printf("a");
rtp_hdr->marker=1;
rtp_hdr->seq_no = htons(g_rtspClients[is].seqnum++); //序列號(hào)诈泼,每發(fā)送一個(gè)RTP包增1懂拾,htons,將主機(jī)字節(jié)序轉(zhuǎn)成網(wǎng)絡(luò)字節(jié)序铐达。
//設(shè)置NALU HEADER,并將這個(gè)HEADER填入sendbuf[12]
nalu_hdr =(NALU_HEADER*)&sendbuf[12]; ////將sendbuf[12]的地址賦給nalu_hdr岖赋,之后對(duì)nalu_hdr的寫入就將寫入sendbuf中; 為什么是sendbuf[12]呢瓮孙?因?yàn)镻T 7字節(jié)唐断,V:2字節(jié),M:1字節(jié)
nalu_hdr->F=0;
nalu_hdr->NRI= nIsIFrm;
nalu_hdr->TYPE= nNaluType;
nalu_payload=&sendbuf[13];
memcpy(nalu_payload,buffer,nAvFrmLen);
/*
tmptime = g_rtspClients[is].tsvid+timestamp_increse;
if(tmptime >= (-intdelta))g_rtspClients[is].tsvid=tmptime + intdelta;
else g_rtspClients[is].tsvid = tmptime;
*/
g_rtspClients[is].tsvid = g_rtspClients[is].tsvid+timestamp_increse;
rtp_hdr->timestamp=htonl(g_rtspClients[is].tsvid);
bytes=nAvFrmLen+ 13 ;
printf("data less than nalu_sent_len\n");
sendto(udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
}
else if(nAvFrmLen>nalu_sent_len)
{
//printf("b");
//μ?μ???naluDèòaó??àéù3¤?è?a1400×??úμ?RTP°üà′·¢?í
int k=0,l=0;
k=nAvFrmLen/nalu_sent_len;
l=nAvFrmLen%nalu_sent_len;
int t=0;
/*
tmptime = g_rtspClients[is].tsvid+timestamp_increse;
if(tmptime >= (-intdelta))g_rtspClients[is].tsvid=tmptime + intdelta;
else g_rtspClients[is].tsvid = tmptime;
*/
g_rtspClients[is].tsvid = g_rtspClients[is].tsvid+timestamp_increse;
while(t<=k)
{
rtp_hdr->seq_no = htons(g_rtspClients[is].seqnum++);
if(t==0)
{
//éè??rtp M ??£?
rtp_hdr->marker=0;
fu_ind =(FU_INDICATOR*)&sendbuf[12];
fu_ind->F= 0;
fu_ind->NRI= nIsIFrm;
fu_ind->TYPE=28;
//éè??FU HEADER,2¢???a??HEADERì?è?sendbuf[13]
fu_hdr =(FU_HEADER*)&sendbuf[13];
fu_hdr->E=0;
fu_hdr->R=0;
fu_hdr->S=1;
fu_hdr->TYPE=nNaluType;
nalu_payload=&sendbuf[14];
memcpy(nalu_payload,buffer,nalu_sent_len);
bytes=nalu_sent_len+14;
printf("start:%d\n",t);
sendto( udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
t++;
}
else if(k==t)
{
//éè??rtp M ??£?μ±?°′?ê?μ?ê?×?oóò???·???ê±??????1
rtp_hdr->marker=1;
fu_ind =(FU_INDICATOR*)&sendbuf[12];
fu_ind->F= 0 ;
fu_ind->NRI= nIsIFrm ;
fu_ind->TYPE=28;
//éè??FU HEADER,2¢???a??HEADERì?è?sendbuf[13]
fu_hdr =(FU_HEADER*)&sendbuf[13];
fu_hdr->R=0;
fu_hdr->S=0;
fu_hdr->TYPE= nNaluType;
fu_hdr->E=1;
nalu_payload=&sendbuf[14];
memcpy(nalu_payload,buffer+t*nalu_sent_len,l);
bytes=l+14;
printf("end:%d\n",t);
sendto(udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
t++;
}
else if(t<k && t!=0)
{
//éè??rtp M ??£?
rtp_hdr->marker=0;
//éè??FU INDICATOR,2¢???a??HEADERì?è?sendbuf[12]
fu_ind =(FU_INDICATOR*)&sendbuf[12];
fu_ind->F=0;
fu_ind->NRI=nIsIFrm;
fu_ind->TYPE=28;
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=nNaluType;
nalu_payload=&sendbuf[14];
memcpy(nalu_payload,buffer+t*nalu_sent_len,nalu_sent_len);
bytes=nalu_sent_len+14;
printf("halfway:%d\n",t);
sendto(udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
t++;
}
}
}
}
//------------------------------------------------------------
}