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

做直播,音视频通讯。经常需要通过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案例就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Java fastjson解析json字符串实现过程解析

    jar的下载 maven方式 <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.67</version> </dependency> jar包 百度云链接:https://pan.baidu.com/s/1x_C4ii3OFMXbsoqikmZKGw 提取码:ku6z 示例 解析j

  • java 中类似js encodeURIComponent 函数的实现案例

    我就废话不多说了,大家还是直接看代码吧~ import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; /** * Utility class for JavaScript compatible UTF-8 encoding and decoding. * * @see http://stackoverflow.com/questions/607176/ja

  • Java并发编程——volatile关键字

    一.volatile是什么 volatile是Java并发编程中重要的一个关键字,被比喻为"轻量级的synchronized",与synchronized不同的是,volatile只能修饰变量,无法修饰方法及代码块等. 下面是使用volatile关键字实现的单例模式: public class Singleton implements Serializable { private static volatile Singleton singleton; private Singleto

  • JAVA包装类及自动封包解包实例代码

    复制代码 代码如下: public class Wrapper {     public static void main(String[] args) {         int i = 500;         Integer t = new Integer(i);         int j = t.intValue();         String s = t.toString();         System.out.println(t);         Integer t1 =

  • JAVA读取文件流,设置浏览器下载或直接预览操作

    最近项目需要在浏览器中通过URL预览图片.但发现浏览器始终默认下载,而不是预览.研究了一下,发现了问题: // 设置response的Header,注意这句,如果开启,默认浏览器会进行下载操作,如果注释掉,浏览器会默认预览. response.addHeader("Content-Disposition", "attachment;filename=" + FileUtil.getOriginalFilename(path)); 然后需要注意: response.s

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

    做直播,音视频通讯.经常需要通过rtp协议封装音视频数据来发送.网上找到的基本都是c或c++版本的,没有JAVA版本的.就算千辛万苦找到一篇java版本的,要么不能用,要么就是一些片段,要么有封包没解包. 很是蛋疼,本人也是这样,刚开始不太熟悉rtp协议,不太明白怎么封包组包,痛苦了几天,终于搞出来了,分享给有需要的朋友,希望对你们有所帮助. 直接看代码吧.不多说了. 首先看看关键类: package com.imsdk.socket.udp.codec; import android.os.S

  • 基于Nodejs的Tcp封包和解包的理解

    我们知道,TCP是面向连接流传输的,其采用Nagle算法,在缓冲区对上层数据进行了处理.避免触发自动分片机制和网络上大量小数据包的同时也造成了粘包(小包合并)和半包(大包拆分)问题,导致数据没有消息保护边界,接收端接收到一次数据无法判断是否是一个完整数据包.那有什么方案可以解决这问题呢? 1.粘包问题解决方案及对比 很简单,既然消息没有边界,那我们在消息往下传之前给它加一个边界识别就好了. 发送固定长度的消息 使用特殊标记来区分消息间隔 把消息的尺寸与消息一块发送 第一种方案不够灵活:第二种有风

  • Android rom解包打包工具

    eMMC主要是针对手机和平板电脑等产品的内嵌式存储器,由于其在封装中集成了一个控制器,且提供标准接口并管理闪存等优势,越来越受到Android手机厂商的青睐,以eMMC为存储设备的android手机,其文件系统(system.data分区)一般采用ext4格式.如小米手机的线刷包: 一.img解包 之前我在修改小米桌面中介绍过利用rom助手解包. 这里介绍另一个更方便实用的工具:windows平台的ext4_unpacker. 直接选择需要解压的img镜像文件,然后extract所有文件即可.同

  • Android studio利用gradle打jar包并混淆的方法详解

    本文主要介绍了Android studio利用gradle打jar包并混淆的方法,下面话不多说,来看看详细的介绍吧. 首先打jar包的配置很简单,使用jar的task,可以参考gradle官方文档,具体代码如下: task buildJar(type: Jar, dependsOn: ['assembleRelease']) { destinationDir = file('build/outputs/jar/') appendix = "" baseName = "&quo

  • Android Studio3.2中导出jar包的过程详解

    1.)说明. 本项目是来自github上的一个项目roottools (https://github.com/Stericson/RootTools),这里只是想本地编译后输出下jar包供自己进行使用. 2.)操作步骤. 步骤1)按之前你熟悉的方式进行开发待输出为jar的项目. 步骤2) 一般的gradle设置,比如gradle版本,android sdk的编译,目标,最小要求版本..还有compileOptions的jdk版本设置等. 步骤3)gradle中的apply plugin设置: a

  • Android SDK中的Support兼容包详解

    背景 来自于知乎上邀请回答的一个问题Android中AppCompat和Holo的一个问题?, 看来很多人还是对这些兼容包搞不清楚,那么干脆写篇博客吧. Support Library 我们都知道Android一些SDK比较分裂,为此google官方提供了Android Support Library package 系列的包来保证高版本sdk开发的向下兼容性, 所以你可能经常看到v4,v7,v13这些数字,首先我们就来理清楚这些数字的含义,以及它们之间的区别. support-v4 用在API

  • java & Android 格式化字符串详解

    %1$s %1$d Android string (java & Android 格式化字符串) 1$s // String %1$d // int //R.string.old: <string name="old">我今年%1$d岁了</string> String sAgeFormat = getResources().getString(R.string.old); String sFinalAge = String.format(sAgeFor

  • Android分包MultiDex策略详解

    1.分包背景 这里首先介绍下MultiDex的产生背景. 当Android系统安装一个应用的时候,有一步是对Dex进行优化,这个过程有一个专门的工具来处理,叫DexOpt.DexOpt的执行过程是在第一次加载Dex文件的时候执行的.这个过程会生成一个ODEX文件,即Optimised Dex.执行ODex的效率会比直接执行Dex文件的效率要高很多. 但是在早期的Android系统中,DexOpt有一个问题,DexOpt会把每一个类的方法id检索起来,存在一个链表结构里面.但是这个链表的长度是用一

  • Android AOP注解Annotation详解(一)

    Android 注解Annotation 相关文章: Android AOP注解Annotation详解(一) Android AOP之注解处理解释器详解(二) Android AOP 注解详解及简单使用实例(三) Android AOP 等在Android上应用越来越广泛,例如框架ButterKnife,Dagger2,EventBus3等等,这里我自己总结了一个学习路程. - Java的注解Annotation - 注解处理解析器APT(Annotation Processing Tool)

随机推荐