源码
HI_S32 VENC_Sent(char *buffer,int buflen)
{
HI_S32 i;
int is=0;
int nChanNum=0;
for(is=0;is<MAX_RTSP_CLIENT;is++)
{
if(g_rtspClients[is].status!=RTSP_SENDING)
{
continue;
}
int heart = g_rtspClients[is].seqnum % 10000;
char* nalu_payload;
int nAvFrmLen = 0;
int nIsIFrm = 0;
int nNaluType = 0;
char sendbuf[500*1024+32];
nAvFrmLen = buflen;
struct sockaddr_in server;
server.sin_family=AF_INET;
server.sin_port=htons(g_rtspClients[is].rtpport[0]);
server.sin_addr.s_addr=inet_addr(g_rtspClients[is].IP);
int bytes=0;
unsigned int timestamp_increse=0;
timestamp_increse=(unsigned int)(90000.0 / 25);
rtp_hdr =(RTP_FIXED_HEADER*)&sendbuf[0];
rtp_hdr->payload = RTP_H264;
rtp_hdr->version = 2;
rtp_hdr->marker = 0;
rtp_hdr->ssrc = htonl(10);
if(nAvFrmLen<=nalu_sent_len)
{
rtp_hdr->marker=1;
rtp_hdr->seq_no = htons(g_rtspClients[is].seqnum++);
nalu_hdr =(NALU_HEADER*)&sendbuf[12];
nalu_hdr->F=0;
nalu_hdr->NRI= nIsIFrm;
nalu_hdr->TYPE= nNaluType;
nalu_payload=&sendbuf[13];
memcpy(nalu_payload,buffer,nAvFrmLen);
g_rtspClients[is].tsvid = g_rtspClients[is].tsvid+timestamp_increse;
rtp_hdr->timestamp=htonl(g_rtspClients[is].tsvid);
bytes=nAvFrmLen+ 13 ;
sendto(udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
}
else if(nAvFrmLen>nalu_sent_len)
{
int k=0,l=0;
k=nAvFrmLen/nalu_sent_len;
l=nAvFrmLen%nalu_sent_len;
int t=0;
g_rtspClients[is].tsvid = g_rtspClients[is].tsvid+timestamp_increse;
rtp_hdr->timestamp=htonl(g_rtspClients[is].tsvid);
while(t<=k)
{
rtp_hdr->seq_no = htons(g_rtspClients[is].seqnum++);
if(t==0)
{
rtp_hdr->marker=0;
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=1;
fu_hdr->TYPE=nNaluType;
nalu_payload=&sendbuf[14];
memcpy(nalu_payload,buffer,nalu_sent_len);
bytes=nalu_sent_len+14;
sendto( udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
t++;
}
else if(k==t)
{
rtp_hdr->marker=1;
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->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;
sendto(udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
t++;
}
else if(t<k && t!=0)
{
rtp_hdr->marker=0;
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;
sendto(udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
t++;
}
}
}
}
//------------------------------------------------------------
}
分析
通过socket接口去发送是比较简单的,但是RTSP发送H264涉及到一个分包的问题,要添加包头
假如包丢了,那只要重新把这个包发一遍就行了;如果不分包,包丢了就要重头开始发
首先对状态进行判断
然后获取heart,用数据结构中的seqnum去对10000取余,超过10000就从头开始计
这个heart类似心跳包
下面定义一些变量。其中nAvFrmLen
,这个变量一看名字,有AV,代表audio、video,frame表示帧,len表示长度。意思就是,这个变量代表视频或者音频一帧的长度
然后进行网络编程常规的填充UDP/TCP的选项、IP地址、端口号
一个重要的变量是timestamp_increse
(RTP中的timestamp)。
每一帧都对应一个RTP的timestamp,帧与帧之间有一个固定间隔,这个间隔就是timestamp_increase。上一帧到下一帧,就是用上一帧的timestamp加上固定的间隔即可。
一般H264的时钟频率是90000,时钟频率除以帧率就是timestamp的固定增量
接着去填充RTP的包头。
- payload——不用多说
- version——RTP的版本号,当前版本号是2,未来若有跟新,这里也要相应进行改动
- marker——当前位置是不是一帧的最后一包,因为有可能一帧被分包发送。对视频是标记一帧的结束,对音频标记一帧的开始
- ssrc——信源标记
以上内容为发送前的配置
以下为发送
长度塞满一包的情况
else if(nAvFrmLen>nalu_sent_len)
{
int k=0,l=0;
k=nAvFrmLen/nalu_sent_len;
l=nAvFrmLen%nalu_sent_len;
int t=0;
g_rtspClients[is].tsvid = g_rtspClients[is].tsvid+timestamp_increse;
rtp_hdr->timestamp=htonl(g_rtspClients[is].tsvid);
while(t<=k)
{
rtp_hdr->seq_no = htons(g_rtspClients[is].seqnum++);
if(t==0)
{
rtp_hdr->marker=0;
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=1;
fu_hdr->TYPE=nNaluType;
nalu_payload=&sendbuf[14];
memcpy(nalu_payload,buffer,nalu_sent_len);
bytes=nalu_sent_len+14;
sendto( udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
t++;
}
else if(k==t)
{
rtp_hdr->marker=1;
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->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;
sendto(udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
t++;
}
else if(t<k && t!=0)
{
rtp_hdr->marker=0;
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;
sendto(udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
t++;
}
}
}
}
一上来就是整除取余,整除是看一共有几包,取余是看剩余的长度
g_rtspClients[is].tsvid
是用来存放timestamp的值,算出来之后转换成网络字节序,放到rtp_hdr
中
然后进一个while循环,一包一包的去发,把能被一包长度整除的包都发完。
- t==0——第一包
- k==t——最后一包
- t<k && t!=0——中间的包
分这么细的原因就是,他们包头的填充格式是不一样的
第一包
准备发第一包的时候可以发现,包头装完之后紧接着的不是有效数据,而是fu_ind,再后面才是有效数据。(参考RTP协议详解中分包的内容)
因为如果h264传输的时候,包太大,会被分为多个片。NALU的头会被如下的两个自己代替
NALU header中的type表示这一个nalu的类型,如果它是一个分片比如type=28(I帧的分片),即FU-A。
fu_ind从12开始,是应为RTP的报头占11个字节(自己编写的代码中需要注意对齐)。
F固定是0;重要性应该解析SPS提取得出,我看的这份代码里不是的,不规范;TYPE表示这是一个分片
下半部分是fu_header。S为1,E不能为1。一个NALU既是开头又是结尾的情况,就是只有它一包,直接发就行了,不用分片。
type是NALU中的type,需要提取解析
然后第14个字节开始填充有效数据
于是结构图如下
最后一个整包
这里rtp头的marker置1,说明这是帧的最后一包
这里的分包和之前一样
nalu被分为fu_ind和fu_header
不同的是fu_header中的End=1
中间的整包
既不是开头也不是结尾,R和S都是0
其他一样
这个情况是正好被一包长度整除,最后一包也塞满。
配置好之后就发出去
长度不够整包
nalu_sent_len
这个值是根据网卡带宽设置的,使用ifconfig指令查看,MTU字段就是网卡带宽。这里我们开发板的带宽是1500,代码中设置了1400
这个判断条件就是剩余的不足1400字节的内容,即最后一包,长度不足1400字节。
marker为1,也就印证了这是一帧的结束
由于最后一包不用分片,所以他是NALU_header。只有NaluTYpe不一样,其他都和FU_ind一样
只有payload放进去,timestamp加上去。
这里只有13个字节,应为不是两个字节的FU_ind而是一个字节的NALU_header
这些数据配置完再放进去就结束了