欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java、android可用的rtp封包解包h264案例

程序员文章站 2022-07-09 16:51:13
做直播,音视频通讯。经常需要通过rtp协议封装音视频数据来发送。网上找到的基本都是c或c++版本的,没有java版本的。就算千辛万苦找到一篇java版本的,要么不能用,要么就是一些片段,要么有封包没解...

做直播,音视频通讯。经常需要通过rtp协议封装音视频数据来发送。网上找到的基本都是c或c++版本的,没有java版本的。就算千辛万苦找到一篇java版本的,要么不能用,要么就是一些片段,要么有封包没解包。

很是蛋疼,本人也是这样,刚开始不太熟悉rtp协议,不太明白怎么封包组包,痛苦了几天,终于搞出来了,分享给有需要的朋友,希望对你们有所帮助。

直接看代码吧。不多说了。

首先看看关键类:

package com.imsdk.socket.udp.codec; 
import android.os.systemclock;
import android.util.log;
 
import java.io.bytearrayinputstream;
import java.io.ioexception;
import java.io.inputstream;
import java.math.bigdecimal;
import java.util.random;
import java.util.concurrent.semaphore;
 
public class rtsppacketencode { 
  private static final string tag = "rtsppacketencode"; 
 
  //------------视频转换数据监听-----------
  public interface h264tortplinsener {
    void h264tortpresponse(byte[] out, int len);
  }
 
  private h264tortplinsener h264tortplinsener;
 
  //执行回调
  private void exceuteh264tortplinsener(byte[] out, int len) {
    if (this.h264tortplinsener != null) {
      h264tortplinsener.h264tortpresponse(out, len);
    }
  } 
 
  // -------视频--------
  private int framerate = 10;
  private byte[] sendbuf = new byte[1500];
  private int packagesize = 1400;
  private int seq_num = 0;
  private int timestamp_increse = (int) (90000.0 / framerate);//framerate是帧率
  private int ts_current = 0;
  private int bytes = 0;
 
  // -------视频end--------
 
  public rtsppacketencode(h264tortplinsener h264tortplinsener) {
    this.h264tortplinsener = h264tortplinsener;
  } 
 
  /**
   * 一帧一帧的rtp封包
   *
   * @param r
   * @return
   */
  public void h264tortp(byte[] r, int h264len) throws exception {
 
    calculateutil.memset(sendbuf, 0, 1500);
    sendbuf[1] = (byte) (sendbuf[1] | 96); // 负载类型号96,其值为:01100000
    sendbuf[0] = (byte) (sendbuf[0] | 0x80); // 版本号,此版本固定为2
    sendbuf[1] = (byte) (sendbuf[1] & 254); //标志位,由具体协议规定其值,其值为:01100000
    sendbuf[11] = 10;//随机指定10,并在本rtp回话中全局唯一,java默认采用网络字节序号 不用转换(同源标识符的最后一个字节)
    if (h264len <= packagesize) {
      sendbuf[1] = (byte) (sendbuf[1] | 0x80); // 设置rtp m位为1,其值为:11100000,分包的最后一片,m位(第一位)为0,后7位是十进制的96,表示负载类型
      sendbuf[3] = (byte) seq_num++;
      system.arraycopy(calculateutil.inttobyte(seq_num++), 0, sendbuf, 2, 2);//send[2]和send[3]为序列号,共两位
      {
        // java默认的网络字节序是大端字节序(无论在什么平台上),因为windows为小字节序,所以必须倒序
        /**参考:
         * http://blog.csdn.net/u011068702/article/details/51857557
         * http://cpjsjxy.iteye.com/blog/1591261
         */
        byte temp = 0;
        temp = sendbuf[3];
        sendbuf[3] = sendbuf[2];
        sendbuf[2] = temp;
      }
      // fu-a header, 并将这个header填入sendbuf[12]
      sendbuf[12] = (byte) (sendbuf[12] | ((byte) (r[0] & 0x80)) << 7);
      sendbuf[12] = (byte) (sendbuf[12] | ((byte) ((r[0] & 0x60) >> 5)) << 5);
      sendbuf[12] = (byte) (sendbuf[12] | ((byte) (r[0] & 0x1f)));
      // 同理将sendbuf[13]赋给nalu_payload
      //nalu头已经写到sendbuf[12]中,接下来则存放的是nal的第一个字节之后的数据。所以从r的第二个字节开始复制
      system.arraycopy(r, 1, sendbuf, 13, h264len - 1);
      ts_current = ts_current + timestamp_increse;
      system.arraycopy(calculateutil.inttobyte(ts_current), 0, sendbuf, 4, 4);//序列号接下来是时间戳,4个字节,存储后也需要倒序
      {
        byte temp = 0;
        temp = sendbuf[4];
        sendbuf[4] = sendbuf[7];
        sendbuf[7] = temp;
        temp = sendbuf[5];
        sendbuf[5] = sendbuf[6];
        sendbuf[6] = temp;
      }
      bytes = h264len + 12;//获sendbuf的长度,为nalu的长度(包含nalu头但取出起始前缀,加上rtp_header固定长度12个字节)
      //client.send(new datagrampacket(sendbuf, bytes, addr, port/*9200*/));
      //send(sendbuf,bytes);
      exceuteh264tortplinsener(sendbuf, bytes);
 
    } else if (h264len > packagesize) {
      int k = 0, l = 0;
      k = h264len / packagesize;
      l = h264len % packagesize;
      int t = 0;
      ts_current = ts_current + timestamp_increse;
      system.arraycopy(calculateutil.inttobyte(ts_current), 0, sendbuf, 4, 4);//时间戳,并且倒序
      {
        byte temp = 0;
        temp = sendbuf[4];
        sendbuf[4] = sendbuf[7];
        sendbuf[7] = temp;
        temp = sendbuf[5];
        sendbuf[5] = sendbuf[6];
        sendbuf[6] = temp;
      }
      while (t <= k) {
        system.arraycopy(calculateutil.inttobyte(seq_num++), 0, sendbuf, 2, 2);//序列号,并且倒序
        {
          byte temp = 0;
          temp = sendbuf[3];
          sendbuf[3] = sendbuf[2];
          sendbuf[2] = temp;
        }
        if (t == 0) {//分包的第一片
          sendbuf[1] = (byte) (sendbuf[1] & 0x7f);//其值为:01100000,不是最后一片,m位(第一位)设为0
          //fu indicator,一个字节,紧接在rtp header之后,包括f,nri,header
          sendbuf[12] = (byte) (sendbuf[12] | ((byte) (r[0] & 0x80)) << 7);//禁止位,为0
          sendbuf[12] = (byte) (sendbuf[12] | ((byte) ((r[0] & 0x60) >> 5)) << 5);//nri,表示包的重要性
          sendbuf[12] = (byte) (sendbuf[12] | (byte) (28));//type,表示此fu-a包为什么类型,一般此处为28
          //fu header,一个字节,s,e,r,type
          sendbuf[13] = (byte) (sendbuf[13] & 0xbf);//e=0,表示是否为最后一个包,是则为1
          sendbuf[13] = (byte) (sendbuf[13] & 0xdf);//r=0,保留位,必须设置为0
          sendbuf[13] = (byte) (sendbuf[13] | 0x80);//s=1,表示是否为第一个包,是则为1
          sendbuf[13] = (byte) (sendbuf[13] | ((byte) (r[0] & 0x1f)));//type,即nalu头对应的type
          //将除去nalu头剩下的nalu数据写入sendbuf的第14个字节之后。前14个字节包括:12字节的rtp header,fu indicator,fu header
          system.arraycopy(r, 1, sendbuf, 14, packagesize);
          //client.send(new datagrampacket(sendbuf, packagesize + 14, addr, port/*9200*/));
          exceuteh264tortplinsener(sendbuf, packagesize + 14);
          t++;
        } else if (t == k) {//分片的最后一片
          sendbuf[1] = (byte) (sendbuf[1] | 0x80);
 
          sendbuf[12] = (byte) (sendbuf[12] | ((byte) (r[0] & 0x80)) << 7);
          sendbuf[12] = (byte) (sendbuf[12] | ((byte) ((r[0] & 0x60) >> 5)) << 5);
          sendbuf[12] = (byte) (sendbuf[12] | (byte) (28));
 
          sendbuf[13] = (byte) (sendbuf[13] & 0xdf); //r=0,保留位必须设为0
          sendbuf[13] = (byte) (sendbuf[13] & 0x7f); //s=0,不是第一个包
          sendbuf[13] = (byte) (sendbuf[13] | 0x40); //e=1,是最后一个包
          sendbuf[13] = (byte) (sendbuf[13] | ((byte) (r[0] & 0x1f)));//nalu头对应的type
 
          if (0 != l) {//如果不能整除,则有剩下的包,执行此代码。如果包大小恰好是1400的倍数,不执行此代码。
            system.arraycopy(r, t * packagesize + 1, sendbuf, 14, l - 1);//l-1,不包含nalu头
            bytes = l - 1 + 14; //bytes=l-1+14;
            //client.send(new datagrampacket(sendbuf, bytes, addr, port/*9200*/));
            //send(sendbuf,bytes);
            exceuteh264tortplinsener(sendbuf, bytes);
          }//pl
          t++;
        } else if (t < k && 0 != t) {//既不是第一片,又不是最后一片的包
          sendbuf[1] = (byte) (sendbuf[1] & 0x7f); //m=0,其值为:01100000,不是最后一片,m位(第一位)设为0.
          sendbuf[12] = (byte) (sendbuf[12] | ((byte) (r[0] & 0x80)) << 7);
          sendbuf[12] = (byte) (sendbuf[12] | ((byte) ((r[0] & 0x60) >> 5)) << 5);
          sendbuf[12] = (byte) (sendbuf[12] | (byte) (28));
 
          sendbuf[13] = (byte) (sendbuf[13] & 0xdf); //r=0,保留位必须设为0
          sendbuf[13] = (byte) (sendbuf[13] & 0x7f); //s=0,不是第一个包
          sendbuf[13] = (byte) (sendbuf[13] & 0xbf); //e=0,不是最后一个包
          sendbuf[13] = (byte) (sendbuf[13] | ((byte) (r[0] & 0x1f)));//nalu头对应的type
          system.arraycopy(r, t * packagesize + 1, sendbuf, 14, packagesize);//不包含nalu头
          //client.send(new datagrampacket(sendbuf, packagesize + 14, addr, port/*9200*/));
          //send(sendbuf,1414);
          exceuteh264tortplinsener(sendbuf, packagesize + 14);
 
          t++;
        }
      }
    }
  }
}

计算类:

package com.imsdk.socket.udp.codec; 
/**
 * 计算类
 *
 * @author kokjuis
 */
public class calculateutil {
 
  /**
   * 注释:int到字节数组的转换!
   *
   * @param number
   * @return
   */
  public static byte[] inttobyte(int number) {
    int temp = number;
    byte[] b = new byte[4];
    for (int i = 0; i < b.length; i++) {
      b[i] = new integer(temp & 0xff).bytevalue();// 将最低位保存在最低位
      temp = temp >> 8; // 向右移8位
    }
    return b;
  } 
 
  public static int bytetoint(byte b) {
    //java 总是把 byte 当做有符处理;我们可以通过将其和 0xff 进行二进制与得到它的无符值
    return b & 0xff;
  } 
 
  //byte 数组与 int 的相互转换
  public static int bytearraytoint(byte[] b) {
    return b[3] & 0xff |
        (b[2] & 0xff) << 8 |
        (b[1] & 0xff) << 16 |
        (b[0] & 0xff) << 24;
  }
 
  public static byte[] inttobytearray(int a) {
    return new byte[] {
        (byte) ((a >> 24) & 0xff),
        (byte) ((a >> 16) & 0xff),
        (byte) ((a >> 8) & 0xff),
        (byte) (a & 0xff)
    };
  } 
 
  // 清空buf的值
  public static void memset(byte[] buf, int value, int size) {
    for (int i = 0; i < size; i++) {
      buf[i] = (byte) value;
    }
  }
 
  public static void dump(nalu_t n) {
    system.out.println("len: " + n.len + " nal_unit_type:" + n.nal_unit_type); 
  }
 
  // 判断是否为0x000001,如果是返回1
  public static int findstartcode2(byte[] buf, int off) {
    if (buf[0 + off] != 0 || buf[1 + off] != 0 || buf[2 + off] != 1)
      return 0;
    else
      return 1;
  }
 
  // 判断是否为0x00000001,如果是返回1
  public static int findstartcode3(byte[] buf, int off) {
    if (buf[0 + off] != 0 || buf[1 + off] != 0 || buf[2 + off] != 0 || buf[3 + off] != 1)
      return 0;
    else
      return 1;
  } 
}

使用的话,实现监听就可以了:

 @override
  public void h264tortpresponse(byte[] out, int len) {
    //h264转rtp监听
 
    if (out != null) {
      log.v(tag, "---发送数据---" + len);
      netsendtask.pushbuf(out, len);      
    }
 }
 
 rtsppacketencode.h264tortp(h264, ret);

组包类:

package com.imsdk.socket.udp.codec; 
public class rtsppacketdecode { 
 
  private byte[] h264buffer;
  private int h264len = 0;
  private int h264pos = 0;
  private static final byte[] start_code = {0, 0, 0, 1};   // h264 start code
 
 //传入视频的分辨率
  public rtsppacketdecode(int width, int height) {
    h264buffer = new byte[getyuvbuffer(width, height)];
  } 
 
  /**
   * rtp解包h264
   *
   * @param rtpdata
   * @return
   */
  public byte[] rtp2h264(byte[] rtpdata, int rtplen) {
 
    int fu_header_len = 12;     // fu-header长度为12字节
    int extension = (rtpdata[0] & (1 << 4)); // x: 扩展为是否为1
    if (extension > 0) {
      // 计算扩展头的长度
      int extlen = (rtpdata[12] << 24) + (rtpdata[13] << 16) + (rtpdata[14] << 8) + rtpdata[15];
      fu_header_len += (extlen + 1) * 4;
    }
    // 解析fu-indicator
    byte indicatortype = (byte) (calculateutil.bytetoint(rtpdata[fu_header_len]) & 0x1f); // 取出low 5 bit 则为fu-indicator type
 
    byte nri = (byte) ((calculateutil.bytetoint(rtpdata[fu_header_len]) >> 5) & 0x03);  // 取出h2bit and h3bit
    byte f = (byte) (calculateutil.bytetoint(rtpdata[fu_header_len]) >> 7);        // 取出h1bit
    byte h264_nal_header;
    byte fu_header;
    if (indicatortype == 28) { // fu-a
      fu_header = rtpdata[fu_header_len + 1];
      byte s = (byte) (rtpdata[fu_header_len + 1] & 0x80);
      byte e = (byte) (rtpdata[fu_header_len + 1] & 0x40);
 
      if (e == 64) {  // end of fu-a
        //zologutil.d("rtpparser", "end of fu-a.....;;;");
        byte[] temp = new byte[rtplen - (fu_header_len + 2)];
        system.arraycopy(rtpdata, fu_header_len + 2, temp, 0, temp.length);
        writedata2buffer(temp, temp.length);
        if (h264pos >= 0) {
          h264pos = -1;
          if (h264len > 0) {
            byte[] h264data = new byte[h264len];
            system.arraycopy(h264buffer, 0, h264data, 0, h264len);
            h264len = 0;
            return h264data;
          }
        }
      } else if (s == -128) { // start of fu-a
        h264pos = 0;   // 指针归0
        writedata2buffer(start_code, 4);    // 写入h264起始码
        h264_nal_header = (byte) ((fu_header & 0x1f) | (nri << 5) | (f << 7));
        writedata2buffer(new byte[]{h264_nal_header}, 1);
        byte[] temp = new byte[rtplen - (fu_header_len + 2)];
        system.arraycopy(rtpdata, fu_header_len + 2, temp, 0, temp.length); // 负载数据
        writedata2buffer(temp, temp.length);
      } else {
        byte[] temp = new byte[rtplen - (fu_header_len + 2)];
        system.arraycopy(rtpdata, fu_header_len + 2, temp, 0, temp.length);
        writedata2buffer(temp, temp.length);
      }
    } else { // nalu
      h264pos = 0;
      writedata2buffer(start_code, 4);
      byte[] temp = new byte[rtplen - fu_header_len];
      system.arraycopy(rtpdata, fu_header_len, temp, 0, temp.length);
      writedata2buffer(temp, temp.length);
      if (h264pos >= 0) {
        h264pos = -1;
        if (h264len > 0) {
          byte[] h264data = new byte[h264len];
          system.arraycopy(h264buffer, 0, h264data, 0, h264len);
          h264len = 0;
          return h264data;
        }
      }
    }
    return null;
  }
 
  private void writedata2buffer(byte[] data, int len) {
    if (h264pos >= 0) {
      system.arraycopy(data, 0, h264buffer, h264pos, len);
      h264pos += len;
      h264len += len;
    }
  }
 
  //计算h264大小
  public int getyuvbuffer(int width, int height) {
    // stride = align(width, 16)
    int stride = (int) math.ceil(width / 16.0) * 16;
    // y_size = stride * height
    int y_size = stride * height;
    // c_stride = align(stride/2, 16)
    int c_stride = (int) math.ceil(width / 32.0) * 16;
    // c_size = c_stride * height/2
    int c_size = c_stride * height / 2;
    // size = y_size + c_size * 2
    return y_size + c_size * 2;
  }
 
}

使用:

byte[] tmp = rtsppacketdecode.rtp2h264(out,len);

以上这篇java、android可用的rtp封包解包h264案例就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。