App与终端设备通信经验五(流媒体传输对码流的解析)

模块一(为什么使用RTP协议):
一提到流媒体传输、一谈到什么视频监控、视频会议、语音电话(VOIP),都离不开RTP协议的应用,但当大家都根据经验或者别人的应用而选择RTP协议的时候,你可曾想过,为什么我们要使用RTP来进行流媒体的传输呢?为什么我们一定要用RTP?难道TCP、UDP或者其他的网络协议不能达到我们的要求么?

RTP与TCP的比较

像TCP这样的可靠传输协议,通过超时和重传机制来保证传输数据流中的每一个bit的正确性,但这样会使得无论从协议的实现还是传输的过程都变得非常的复杂。而且,当传输过程中有数据丢失的时候,由于对数据丢失的检测(超时检测)和重传,会数据流的传输被迫暂停和延时。

或许你会说,我们可以利用客户端构造一个足够大的缓冲区来保证显示的正常,这种方法对于从网络播放音视频来说是可以接受的,但是对于一些需要实时交互的场合(如视频聊天、视频会议等),如果这种缓冲超过了200ms,将会产生难以接受的实时性体验。

RTP协议是一种基于UDP的传输协议,RTP本身并不能为按顺序传送数据包提供可靠的传送机制,也不提供流量控制或拥塞控制,它依靠RTCP提供这些服务。这样,对于那些丢失的数据包,不存在由于超时检测而带来的延时,同时,对于那些丢弃的包,也可以由上层根据其重要性来选择性的重传。比如,对于I帧、P帧、B帧数据,由于其重要性依次降低,故在网络状况不好的情况下,可以考虑在B帧丢失甚至P帧丢失的情况下不进行重传,这样,在客户端方面,虽然可能会有短暂的不清晰画面,但却保证了实时性的体验和要求。

RTP协议支持多播技术,节省了带宽
(1)RTP协议在设计上考虑到安全功能,支持加密数据和身份验证功能。
(2)有较少的首部开销
TCP和XTP相对RTP来说具有过多的首部开销(TCP和XTP3.6是40字节,XTP4.0是32字节,而RTP只有12字节)

模块二:本项目的RTP详情。

在这里插入图片描述
在这里插入图片描述

结合实际的代码,来看看解析的过程。

SClientInputThreadTM{
......
    public void run() {
      while (isStart) {
                int result = dis.read(b);
                if (result==-1){
                    Log.e("@@","@@接受数据的长度---------:"+result);
                    //出现-1为没数据情况  长时间没数据断开服务器  暂定10000ms
                    if (outtime==0) {
                        outtime = new Date().getTime();
                    }else{
                        if (new Date().getTime()-outtime>=10000){
                            messageListener.Message("outTime");
                            return;
                        }
                    }
                }else {
                    Log.e(TAG,"@@@接受数据的长度--%"+result+"码流返回"+""+StringUtils.bytesToHexString(b));
                    outtime=0;
                    setdata(b, result);
                }
             }
	}

}

当read方法接收到数据后,会传入接收数据的长度和相应的字节数组,执行setdata()方法。

    private void setdata(byte[] data,int len){
    
            int alllen=len+frontdatalength;//待处理的数据长度  包括上一次缓存的长度
            byte[] temp=new byte[alllen];//新的字节数组的长度
            System.arraycopy(frontdata,0,temp,0,frontdatalength);//先把缓存的字节数组放入到temp中
            System.arraycopy(data,0,temp,frontdatalength,len);//再把来的待处理的数据也加入进来
            frontdatalength=0;//清空掉缓存数据
            int header=isHeader(temp);//30316364出现的位置,根据前面的文档定义,知道这个方法是用来判断是否是帧头的。如果是帧头,那么返回的是帧头在temp数组的位置
            Log.e("@@@","@@@header"+header+"--alllen:"+alllen+"--frontdatalength:"+frontdatalength);//实时的打印出帧头的位置header,数据的总长度alllen,前一帧的缓存长度。
             if (header==-1){//如果没有找到帧头,那么就说明这包数据没有帧头信息,把数据反向拷贝到frontdata中
            frontdatalength=alllen;
            System.arraycopy(temp,0,frontdata,0,alllen);
            return;
        }
         int headers=15+header;//RTP头能够拿到数据类型,这里加上15是为了直接到达时间戳的位置
                 if (alllen>headers){//待处理的数据长度大于当前索引到的长度
            String stypeF = getData(temp, header+15, 1);//rtp头数据类型,只有4bit,前4bits用于标记数据类型,另外后面的4bit用于分包标记,当前未处理分包
  			String stype=stypeF.substring(0,1);//数据类型。这里拿到的应该是二进制0000对应的十进制数据
            String fbtype=stypeF.substring(1,2);//分包类型,分包数据暂时没有做处理
              int length=0;//数据长度
              int alllength=0;
                if (stype.equals("3")) {//为音频),RTP头26字节,长度标志位在第24字节
                headers=header+26;
                 if(alllen>(headers)) {
                    String slength = getData(temp, header + 24, 2);//rtp头数据长度信息
                    length = Integer.parseInt(slength, 16);//数据长度
                    alllength=length+headers;//数据从开始到结尾的长度
                }else {//如果长度没有达到数据体,那么继续作为缓存
                    frontdatalength=alllen;
                    System.arraycopy(temp,0,frontdata,0,alllen);
                    return;
                }else{//为视频RTP头30字节,长度标志位在第28字节
                headers=header+30;
                if(alllen>(headers)) {
                    String slength = getData(temp, header + 28, 2);//rtp头数据长度信息
                    length = Integer.parseInt(slength, 16);//数据长度
                    alllength=length+headers;//数据从开始到结尾的长度
                }else {//如果长度没有达到数据体,那么继续作为缓存
                    frontdatalength=alllen;
                    System.arraycopy(temp,0,frontdata,0,alllen);
                    return;
                }
                //以上都是一些对数据长度检测的判断,那么接下来会把数据加进来
                 if(alllength==alllen){//刚刚好为一个数据段,接收线程接收到的数据的长度,恰好等同于截取到的head和数据体的长度。那么恰好为一个数据段
					  if (stype.equals("3")){//为音频
					  String  currenttimestamp = getData(temp, header + 16, 8);//拿到时间戳
					    timestamp = currenttimestamp;
                        if (oneg726length>0){
                            byte[] g726temp = new byte[oneg726length];
                            System.arraycopy(oneg726, 0, g726temp, 0, oneg726length);
                            allg726.add(g726temp);
                            oneg726length = 0;
                        }
                           System.arraycopy(temp,header+26,oneg726,oneg726length,alllen-header-26);//4字节的海思头在这去掉了
                    oneg726length=oneg726length+alllen-header-26;
					  }else{//为视频
					      System.arraycopy(temp,header+30,onemp4,onemp4length,alllen-header-30);
                    onemp4length=onemp4length+alllen-header-30;
                    String  currenttimestamp = getData(temp, header + 16, 8);
                    if (!currenttimestamp.equals(timestamp)) {//新的一个(音视频包,帧)开始
                        timestamp = currenttimestamp;
                    }
                    if (fbtype.equals("0")||fbtype.equals("2")){//原子包或最后一个包
                        byte[] mp4temp = new byte[onemp4length];
                        System.arraycopy(onemp4, 0, mp4temp, 0, onemp4length);
                        allmp4.add(mp4temp);
                        onemp4length = 0;
                    }
			}else if(alllength>alllen){//不足一个数据段  留到下次解析
                frontdatalength=alllen;
                System.arraycopy(temp,0,frontdata,0,alllen);
				 }else{//超过一个数据段  继续解析
                if (stype.equals("3")){//为音频
                    String  currenttimestamp = getData(temp, header + 16, 8);
                    if (!currenttimestamp.equals(timestamp)) {//新的一个(音视频包,帧)开始
                        timestamp = currenttimestamp;
                        if (oneg726length>0){
                            byte[] g726temp = new byte[oneg726length];
                            System.arraycopy(oneg726, 0, g726temp, 0, oneg726length);
                            allg726.add(g726temp);
                            oneg726length = 0;
                        }
                    }
                    System.arraycopy(temp,header+26,oneg726,oneg726length,length);
                    oneg726length=oneg726length+length;

                }else{//为视频
                    System.arraycopy(temp,header+30,onemp4,onemp4length,length);
                    onemp4length=onemp4length+length;
                    String  currenttimestamp = getData(temp, header + 16, 8);
                    if (!currenttimestamp.equals(timestamp)) {//新的一个(音视频包,帧)开始
                        timestamp = currenttimestamp;
                    }
                    if (fbtype.equals("0")||fbtype.equals("2")){//原子包或最后一个包
                        byte[] mp4temp = new byte[onemp4length];
                        System.arraycopy(onemp4, 0, mp4temp, 0, onemp4length);
                        allmp4.add(mp4temp);
                        onemp4length = 0;
                    }
//                    allmp4.add(ttemp);
                  }
                  //继续解析剩余的,有个递归的思想
                byte[] surplustemp=new byte[alllen-alllength];
                System.arraycopy(temp,alllength,surplustemp,0,alllen-alllength);
                setdata(surplustemp,alllen-alllength);
                }

                }
 
}                 
//成员变量
  byte[] onemp4=new byte[bytelength];//存放一帧视频流
    byte[] oneg726=new byte[audiobyteLength];//存放一帧音频流
    int onemp4length=0;//存放一帧视频流当前长度
    int oneg726length=0;//存放一帧音频流当前长度
    String timestamp;//音视频的时间戳
 /**
     * 判断数据头
     * @param temp
     * @return
     */
    private int isHeader(byte[] temp) {
        int i = 0, j = 1;
        byte[] header = {0x30, 0x31, 0x63, 0x64};
        if (temp.length > 4) {
//            Log.v("收到消息", "@@msg" +"  "+temp[0]+" "+temp[1]+" "+temp[2]+" "+temp[3]);
            for (i = 0; i < temp.length - 4; i++) {
                if (temp[i] == header[0]) {
                    for (j = 1; j < header.length; j++) {
                        if (temp[i + j] != header[j]) {
                            break;
                        }
                        if (j == header.length - 1) {
                            return i;
                        }
                    }
                }
            }
        } else {
            return -1;
        }
        return -1;
    }

			
    }

整个的解析过程有个递归的思想。通过比对当前ServerSocket收到的长度和。数据项的长度。如果两项长度相同的话就就正常的解析。
这里需要考虑终端发送数据的各种情况 。一下可能接受到几包或者一包中的一个片段。时间戳增量作为一个包和的分割。通过分包标志(是否是原子包或者最后一包),作为一帧数据的标志

下片介绍对解析到的数据的进一步的处理

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值