2018-03-06

从摄像头获取的视频数据,经过编码后(当然,也可以不编码,如果你觉得也很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格式组成,见下图:

2018-03-06_第1张图片
image

各个字段代表含义如下:

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解码错误)。

2018-03-06_第2张图片
image

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荷载格式:

2018-03-06_第3张图片
image

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。

2018-03-06_第4张图片
image

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_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(tmarker=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++;
                }
            }
        }

    }

    //------------------------------------------------------------
}

你可能感兴趣的:(2018-03-06)