RTP实时视频传输,是基于海思的sample实现的。实际就是接入ORTP,将编码后的H264视频流通过ORTP的API传出去,再通过VLC实时预览。
实际修改sample的代码,是参考ortp库中的/src/tests/rtpsend.c
代码进行编写的。ortp源码阅读
首先,在sample编码初始化部分,即从venc通道中拿码流准备文件存储部分之前,将ORTP初始化
#if ORTP_ENABLE /***rtp init****/ pRtpSession = rtpInit( LOCAL_HOST_IP ,8080); if (pRtpSession==NULL) { printf( "error rtpInit" ); exit(-1); return 0; } #endif
初始化部分的内容和实例代码一致
设置HI3518E为仅发送、使用调度器管理会话、无阻塞模式、连接模式、设置接收者的IP地址及端口号,还有码流的payload
接下来,示例中的代码直接调用了rtp_session_send_with_ts(session,buffer,i,user_ts);
进行发送,于是我们实际项目中的代码只需要拿出码流送到接口就可以进行发送
不难推测,在自己包的rtpSend中,最终调用的是rtp_session_send_with_ts
,只是在函数中有一些分包处理。
为什么存储需要计算偏移量,网传不用考虑?
网传发第二包时,第一包已经从传走了,所以可以往同一个地方发。这就是流stream。
接下来分析最关键的rtpSend
函数,关键点就在于分包处理
一次发送的最大长度定义了一个宏MAX_RTP_PKT_LENGTH
,定了1400字节。
这个1400是根据RTP协议来的,在RTP协议指定的时候规定了一个范围。
这个数据肯定不是具体的,是一个范围内的,同时也需要根据实际情况进行调整。
- 如果网络差,发包经常收不到,就需要把
MAX_RTP_PKT_LENGTH
长度缩小,就算丢包重传压力也不大。- 如果网络好,发包都能收到,那么就可以把
MAX_RTP_PKT_LENGTH
设长一点,这样效率高
以上为朱有鹏老师的说法,以下是我宇哥的解释
这个数值是依据网卡的传输能力设置的,使用
ifconfig
指令可以查看网卡的最大传输能力,而后根据网络情况进行修改。
如果数据量小于MAX_RTP_PKT_LENGTH
,那么一包就发完了,不用操心,直接调用rtp的发送接口即可
//如果数据小于MAX_RTP_PKT_LENGTH字节,直接发送:单一NAL单元模式 if(valid_len <= MAX_RTP_PKT_LENGTH) { sendBytes = rtp_session_send_with_ts(session, &buffer[4], valid_len, g_userts); }
上面要注意的是,发送的起始地址是&buffer[4]
,即从第五个字节开始发,这是因为前四个字节是NALU的开头,可以不用,所以直接从NALU开始发送,详情见RTP协议详解(荷载H264)以及我写的h264专栏就能知道前4个字节的作用
只有一帧的时候可以这样干,因为我们不需要考虑包头包尾的问题,只有单一的NALU。
这样操作也是要看接收方认不认这个操作,比如我们用的是VLC作为接收方,VLC是直接看NALU的,所以这边这样操作可以
比较复杂的情况就是整体的数据超过MAX_RTP_PKT_LENGTH
,比如数据8000字节,那么我们要发8000/1400+1
次才能发完。
这时候就需要讲数据拆分成很多个包,一个一个的进行发送。
else if (valid_len > MAX_RTP_PKT_LENGTH) { //切分为很多个包发送,每个包前要对头进行处理,如第一个包 valid_len -= 1; int k=0,l=0; k=valid_len/MAX_RTP_PKT_LENGTH; l=valid_len%MAX_RTP_PKT_LENGTH; int t=0; int pos=5; if(l!=0) { k=k+1; } while(t<k)//||(t==k&&l>0)) { if(t<(k-1))//(t<k&&l!=0)||(t<(k-1))&&(l==0))//(0==t)||(t<k&&0!=l)) { buffer[pos-2]=(NALU & 0x60)|28; buffer[pos-1]=(NALU & 0x1f); if(0==t) { buffer[pos-1]|=0x80; } sendBytes = rtp_session_send_with_ts(session, &buffer[pos-2], MAX_RTP_PKT_LENGTH+2, g_userts); t++; pos+=MAX_RTP_PKT_LENGTH; } else //if((k==t&&l>0)||((t==k-1)&&l==0)) { int iSendLen; if(l>0) { iSendLen=valid_len-t*MAX_RTP_PKT_LENGTH; } else iSendLen=MAX_RTP_PKT_LENGTH; buffer[pos-2]=(NALU & 0x60)|28; buffer[pos-1]=(NALU & 0x1f); buffer[pos-1]|=0x40; sendBytes = rtp_session_send_with_ts(session, &buffer[pos-2], iSendLen+2, g_userts); t++; } } }
代码中首先讲实际长度减1,将NALU剪掉了
接下来开始计算分包数量
k=valid_len/MAX_RTP_PKT_LENGTH; l=valid_len%MAX_RTP_PKT_LENGTH;
以数据量8000byte为例,一包1400byte,就要传5.7次,即分6个包。k就是几包完整的,l就是余下一包中要发的数据量
下面判断l,如果l不是0,那k要加一,即多发一包
while循环条件中的t,是发送的次数,每次发完自加,到次数后停止循环发送。
while循环中,一进去就对t<(k-1)
进行判断,以8000为例,就是把前面5次捞出来,前面的是发完整的1400字节。
如果t<(k-1)
,这时要注意发送的起始位置是&buffer[pos-2]
,发送的长度是MAX_RTP_PKT_LENGTH+2
。这两个字节的内容是
buffer[pos-2]=(NALU & 0x60)|28; buffer[pos-1]=(NALU & 0x1f);
参考NALU结构分析,NALU的8个BIT是按位来进行分析的
于是
buffer[pos-2]=(NALU & 0x60)|28;
就是把当前NALU的重要性改成11,分片单元设为FU-A,就是一帧分单包传输,如果是29,就是一帧分多包。
buffer[pos-1]=(NALU & 0x1f);
和0x0001 1111,就将原来的帧类型留下来
实践代码中有一个细节,在传输第一包数据时,将第一包数据的第一位置为1标记为无效,即丢弃这一包数据不用
if(0==t) { buffer[pos-1]|=0x80; }
以上是前面几包完整数据的传法
最后一包不完整的数据包传法如下
else //if((k==t&&l>0)||((t==k-1)&&l==0)) { int iSendLen; if(l>0) { iSendLen=valid_len-t*MAX_RTP_PKT_LENGTH; } else iSendLen=MAX_RTP_PKT_LENGTH; buffer[pos-2]=(NALU & 0x60)|28; buffer[pos-1]=(NALU & 0x1f); buffer[pos-1]|=0x40; sendBytes = rtp_session_send_with_ts(session, &buffer[pos-2], iSendLen+2, g_userts); t++; }
进else的是最后一包数据,对齐进行判断,如果不是整包,就求得其剩余长度放在iSendLen
中,否则iSendLen
是整包长度即MAX_RTP_PKT_LENGTH
。
接着和上面一样,设置NALU属性
buffer[pos-2]=(NALU & 0x60)|28; buffer[pos-1]=(NALU & 0x1f);
然后再将前一位的重要性置1buffer[pos-1]|=0x40;
最后g_userts
计算时间戳的增量
g_userts += DefaultTimestampIncrement;//timestamp increase
增量是9000/帧率
,是h264中定义的标准