从摄像头获取的视频数据,经过编码后(当然,也可以不编码,如果你觉得也很ok的话),可以视频录制,同时如果需要,当然也可以视频远程传输咯,而实时传输协议(Real-time Transport Protocol,RTP)是在Internet上处理多媒体数据流的一种网络协议,利用它能够在一对一(unicast,单播)或者一对多(multicast,多播)的网络环境中实现传流媒体数据的实时传输(不需要下载完毕后才能看视频)。RTP通常使用UDP来进行多媒体数据的传输,但如果需要的话可以使用TCP等其它协议,**整个RTP协议由两个密切相关的部分组成:RTP数据协议和RTCP控制协议。
RTP数据协议**负责对流媒体数据进行封包并实现媒体流的实时传输,每一个RTP数据报都由头部(Header)和负载(Payload)两个部分组成,其中头部前12个字节的含义是固定的,而负载则可以是音频或者视频数据。
RTCP 控制协议需要与RTP数据协议一起配合使用,当应用程序启动一个RTP会话时将同时占用两个端口,分别供RTP和RTCP使用。RTP本身并不能为按序传输数据包提供可靠的保证,也不提供流量控制和拥塞控制,这些都由RTCP来负责完成。通常RTCP会采用与RTP相同的分发机制,向会话中的所有成员周期性地发送控制信息,应用程序通过接收这些数据,从中获取会话参与者的相关资料,以及网络状况、分组丢失概率等反馈信息,从而能 够对服务质量进行控制或者对网络状况进行诊断。
实时流协议(RealTime Streaming Protocol,RTSP),它的意义在于使得实时流媒体数据的受控和点播变得可能。总的说来,RTSP是一个流媒体表示协议, 主要用来控制具有实时特性的数据发送,但它本身并不传输数据,而是必须依赖于下层传输协议所提供的某些服务。RTSP 可以对流媒体提供诸如播放、暂停、快进**等操作,它负责定义具体的控制消息、操作方法、状态码等,此外还描述了与RTP间的交互操作。
The RTP header has the following format:
RTP header格式组成,见下图:
各个字段代表含义如下:
V:版本号,一般为2;
P:填充字段标识;
X:扩展头标识;
CC:CSRC计数,4比特
M:标志 1bit,在传输h264时表示h264 nalu的最后一包
PT:负载类型 7 bits, H264类型为96,荷载类型的赋值或者通过profile或者通过动态方式
SN:序列号16 bits
Timestamp:时间戳32bits,如果为视频的话,应该设置为1/9000,音频为1/8000,如果NAL单元没有
他自己的时间属性(即,parameter set and SEI NAL units),RTP时戳设置成访问单元主 编码图像的RTP时戳。
SSRC:32bits,用以识别同步源。
CSRC列表:0到15项,每项32比特,CSRC列表识别在此包中负载的所有贡献源。识别符的数目在CC域中
给定。若有贡献源多于15个,仅识别15个。CSRC识别符由混合器插入,并列出所有贡献源的 SSRC识别符。例如语音包,混合产生新包的所有源的SSRC标识符都被列出,这样可以在接收端正确指示参与者。
contributing source (CSRC) identifiers 的组成:如下:
[StartCode] [NALU Header] [NALU Payload]
关于NALU使用RTP包进行发送可能的类型有:
1. 单一 NAL 单元模式(NALU的长度小于 MTU 大小的包)
即一个 RTP 包仅由一个完整的 NALU 组成。这种情况下 RTP NAL 头类型字段和原始的H.264的NALU头类型字段是一样的。
对于 NALU的长度小于 MTU 大小的包,一般采用单一 NAL 单元模式。对于一个原始的H.264 NALU 单元常由 [StartCode] [NALU Header] [NALU Payload] 三部分组成,其中 Start Code 用于标示这是一个NALU单元的开始,必须是"00 00 00 01" 或"00 00 01", NALU 头仅一个字节,其后都是 NALU 单元内容。打包时去除 "00 00 01" 或"00 00 00 01" 的开始码,把其他数据封包的 RTP 包即可,有如下例子:
[00 0000 01 67 42 A0 1E 23 56 0E 2F ... ]
封装成 RTP 包将如下:
[ RTPHeader ] [ 67 42 A0 1E 23 56 0E 2F ]
(在这里要说明的是,如果客户端是通用的播放器,比如VLC或者JM的话需要将前导码去掉,但是如果使用的是ffmpeg在客户端解码的话,发送前不需要去掉前导码,去掉之后可能会导致ffmpeg解码错误)。
H.264Payload 格式定义了三种不同的基本的负载(Payload)结构,接收端可能通过RTP Payload的第一个字节来识别它们。这一个字节类似NALU 头的格式,而这个头结构的NAL 单元类型字段则指出了代表的哪一种结构,这个字节的结构如下:
F 1比特
NRI 2比特
Type 5比特
可以看出它和H.264 的NALU 头结构是一样的。
字段Type:这个RTP payload 中 NAL 单元的类型。 这个字段和 H.264 中类型字段的区别是,当type的值为24-31表示这是一个特别格式的 NAL 单元,而H.264中,只取1-23是有效的值。
2. 分片封包模式(NALU的长度大于 MTU 大小的包)
用于把一个 NALU单元封装成多个 RTP 包。存在两种类型 FU-A 和 FU-B。类型值分别是 28 和 29。 而当 NALU 的长度超过 MTU 时,就必须对 NALU 单元进行分片封包。 也称为Fragmentation Units(FUs)。 将NALU拆分成小于MTU的数据包进行发送,如果使用的是VLC等网络播放器的话,需要设置FU header,如下图所示:
如果使用的是ffmpeg自行进行数据包接收与解码,则完全不必写FU header。其实在后面的实际操作中会发现,SPS、PPS都是非常小,不到一百个字节,都是单个的NAl进行打包发送,而I帧一般都比较大,会采用分包发送,一般也是FU-A方式分片,其中MTU一般是1500个字节。FFmpeg中都有现成的源程序可以参考的。
对于H264的I帧、P帧等主要是FU(分片)发送,那么FU到底是怎样一个过程呢。
相同NAL单元的分片必须使用递增的RTP序号连续顺序发送(第一和最后分片之间没有其他的RTP包)。相似, NAL单元必须按照RTP顺序号的顺序装配。FUs不可以嵌套。即 一个FU 不可以包含另一个FU。运送FU的RTP时戳被设置成分片NALU的NALU的时刻。
FU-A的RTP荷载格式:
NRI: 2 bits, 00值指示NAL单元的内容不用于重建影响图像的帧间图像预测.这样的NAL单元可以被丢弃而不用冒影响图像完整性的风险。大于00的值指示NAL单元的解码要求维护引用图像的完整性。
注意:任何非零的NRI在H.264 解码器的处理是相同的。因此,接收者在传送NAL单元给解码器时不必操作NRI的值。NRI值必须根据分片NAL单元的NRI值设置。H.264编码器必须根据H.264规范设置NRI值。
当nal_unit_type 范围的是1到12。特别是,H.264规范要求对于nal_unit_type为6,9,10,11,12的NAL单元的NRI的值应该为0。
对于nal_unit_type等于7,8 (指示顺序参数集或图像参数集)的NAL单元,H.264编码器应该设置NRI为11 (二进制格式)。
对于nal_unit_type等于5的主编码图像的编码片NAL单元(指示编码片属于一个IDR图像), H.264编码器应设置NRI为11。
S: (1 bit)
当设置成1,开始位指示分片NAL单元的开始。当跟随的FU荷载不是分片NAL单元荷载的开始,开始位设为0。
E: (1 bit)
当设置成1,结束位指示分片NAL单元的结束,即荷载的最后字节也是分片NAL单元的最后一个字节。
当跟随的FU荷载不是分片NAL单元的最后分片,结束位设置为0。
R: (1 bit)
保留位必须设置为0,接收者必须忽略该位。
Type: (5 bit)
NAL单元荷载类型定义。
FU payload : 分片单元荷载。
拷贝海思对应代码如下:
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);//获得系统当前的时间
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))); //时间戳增量的计算;tv结构体的tv_sec(秒)成员乘以1000加上tv_usec(微秒)除以1000,单位统一化成毫秒
memcpy(&tv_pre, &tv, sizeof(tv_pre));
rtp_hdr =(RTP_FIXED_HEADER*)&sendbuf[0]; 将sendbuf[0]d的地址指向rtp_hdr,填充rtp_hdr结构体的内容是通过往sendbuf中写
rtp_hdr->payload = RTP_H264; //负载类型
rtp_hdr->version = 2; //版本号
rtp_hdr->marker = 0; //最后一个NALU时,该值设置成1,其他都设置成0。
rtp_hdr->ssrc = htonl(10); //
// 当一个NALU小于1400字节的时候,采用一个单RTP包发送
if(nAvFrmLen<=nalu_sent_len)
{
//printf("a");
rtp_hdr->marker=1;
rtp_hdr->seq_no = htons(g_rtspClients[is].seqnum++); //序列号,每发送一个RTP包增1,htons,将主机字节序转成网络字节序。
//设置NALU HEADER,并将这个HEADER填入sendbuf[12]
nalu_hdr =(NALU_HEADER*)&sendbuf[12]; ////将sendbuf[12]的地址赋给nalu_hdr,之后对nalu_hdr的写入就将写入sendbuf中; 为什么是sendbuf[12]呢?因为PT 7字节,V:2字节,M:1字节
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++;
}
}
}
}
//------------------------------------------------------------
}