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格式组成,见下图:

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

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

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。

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

    }

    //------------------------------------------------------------
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • 网络抽象层单元类型 (NALU): NALU头由一个字节组成,它的语法如下: F: 1个比特. forbidde...
    ai___believe阅读 8,106评论 0 9
  • 个人觉得比较常用的Linux命令,仅供参考: 系统信息 arch 显示机器的处理器架构(1) uname -m 显...
    晚安丿阅读 195评论 0 0
  • 今天群里有位群友写了篇关《百鸟朝凤》有感,刚好中午我也看了,正好也说两句。 电影里,有一幕给我印象挺深的。唢呐班被...
    比如蓝天阅读 285评论 0 0
  • 如今要形容谁曾经受的伤害太深、经受的压力太大,逢人就说自己曾经的遭遇,以证明自己的清白或发泄自己的苦恼,常说你怎么...
    李在在阅读 379评论 1 2
  • 我入住客栈差不多一个半月了吧~因为一次偶然的机会,我来到了这里,认识了一群有趣、有爱、有梦想的人! 我是一个比较闲...
    浅夏時光阅读 236评论 0 3