java、android可用的rtp封包解包h264案例
做直播,音視頻通訊。經(jīng)常需要通過rtp協(xié)議封裝音視頻數(shù)據(jù)來發(fā)送。網(wǎng)上找到的基本都是c或c++版本的,沒有JAVA版本的。就算千辛萬苦找到一篇java版本的,要么不能用,要么就是一些片段,要么有封包沒解包。
很是蛋疼,本人也是這樣,剛開始不太熟悉rtp協(xié)議,不太明白怎么封包組包,痛苦了幾天,終于搞出來了,分享給有需要的朋友,希望對你們有所幫助。
直接看代碼吧。不多說了。
首先看看關(guān)鍵類:
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";
//------------視頻轉(zhuǎn)換數(shù)據(jù)監(jiān)聽-----------
public interface H264ToRtpLinsener {
void h264ToRtpResponse(byte[] out, int len);
}
private H264ToRtpLinsener h264ToRtpLinsener;
//執(zhí)行回調(diào)
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); // 負(fù)載類型號96,其值為:01100000
sendbuf[0] = (byte) (sendbuf[0] | 0x80); // 版本號,此版本固定為2
sendbuf[1] = (byte) (sendbuf[1] & 254); //標(biāo)志位,由具體協(xié)議規(guī)定其值,其值為:01100000
sendbuf[11] = 10;//隨機(jī)指定10,并在本RTP回話中全局唯一,java默認(rèn)采用網(wǎng)絡(luò)字節(jié)序號 不用轉(zhuǎn)換(同源標(biāo)識符的最后一個(gè)字節(jié))
if (h264len <= packageSize) {
sendbuf[1] = (byte) (sendbuf[1] | 0x80); // 設(shè)置rtp M位為1,其值為:11100000,分包的最后一片,M位(第一位)為0,后7位是十進(jìn)制的96,表示負(fù)載類型
sendbuf[3] = (byte) seq_num++;
System.arraycopy(CalculateUtil.intToByte(seq_num++), 0, sendbuf, 2, 2);//send[2]和send[3]為序列號,共兩位
{
// java默認(rèn)的網(wǎng)絡(luò)字節(jié)序是大端字節(jié)序(無論在什么平臺上),因?yàn)閣indows為小字節(jié)序,所以必須倒序
/**參考:
* 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, 并將這個(gè)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頭已經(jīng)寫到sendbuf[12]中,接下來則存放的是NAL的第一個(gè)字節(jié)之后的數(shù)據(jù)。所以從r的第二個(gè)字節(jié)開始復(fù)制
System.arraycopy(r, 1, sendbuf, 13, h264len - 1);
ts_current = ts_current + timestamp_increse;
System.arraycopy(CalculateUtil.intToByte(ts_current), 0, sendbuf, 4, 4);//序列號接下來是時(shí)間戳,4個(gè)字節(jié),存儲后也需要倒序
{
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個(gè)字節(jié))
//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);//時(shí)間戳,并且倒序
{
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位(第一位)設(shè)為0
//FU indicator,一個(gè)字節(jié),緊接在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,一個(gè)字節(jié),S,E,R,TYPE
sendbuf[13] = (byte) (sendbuf[13] & 0xBF);//E=0,表示是否為最后一個(gè)包,是則為1
sendbuf[13] = (byte) (sendbuf[13] & 0xDF);//R=0,保留位,必須設(shè)置為0
sendbuf[13] = (byte) (sendbuf[13] | 0x80);//S=1,表示是否為第一個(gè)包,是則為1
sendbuf[13] = (byte) (sendbuf[13] | ((byte) (r[0] & 0x1f)));//TYPE,即NALU頭對應(yīng)的TYPE
//將除去NALU頭剩下的NALU數(shù)據(jù)寫入sendbuf的第14個(gè)字節(jié)之后。前14個(gè)字節(jié)包括:12字節(jié)的RTP Header,F(xiàn)U indicator,F(xiàn)U 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,保留位必須設(shè)為0
sendbuf[13] = (byte) (sendbuf[13] & 0x7F); //S=0,不是第一個(gè)包
sendbuf[13] = (byte) (sendbuf[13] | 0x40); //E=1,是最后一個(gè)包
sendbuf[13] = (byte) (sendbuf[13] | ((byte) (r[0] & 0x1f)));//NALU頭對應(yīng)的type
if (0 != l) {//如果不能整除,則有剩下的包,執(zhí)行此代碼。如果包大小恰好是1400的倍數(shù),不執(zhí)行此代碼。
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位(第一位)設(shè)為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,保留位必須設(shè)為0
sendbuf[13] = (byte) (sendbuf[13] & 0x7F); //S=0,不是第一個(gè)包
sendbuf[13] = (byte) (sendbuf[13] & 0xBF); //E=0,不是最后一個(gè)包
sendbuf[13] = (byte) (sendbuf[13] | ((byte) (r[0] & 0x1f)));//NALU頭對應(yīng)的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++;
}
}
}
}
}
計(jì)算類:
package com.imsdk.socket.udp.codec;
/**
* 計(jì)算類
*
* @author kokJuis
*/
public class CalculateUtil {
/**
* 注釋:int到字節(jié)數(shù)組的轉(zhuǎn)換!
*
* @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 當(dāng)做有符處理;我們可以通過將其和 0xFF 進(jìn)行二進(jìn)制與得到它的無符值
return b & 0xFF;
}
//byte 數(shù)組與 int 的相互轉(zhuǎn)換
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;
}
}
使用的話,實(shí)現(xiàn)監(jiān)聽就可以了:
@Override
public void h264ToRtpResponse(byte[] out, int len) {
//h264轉(zhuǎn)rtp監(jiān)聽
if (out != null) {
Log.v(TAG, "---發(fā)送數(shù)據(jù)---" + 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字節(jié)
int extension = (rtpData[0] & (1 << 4)); // X: 擴(kuò)展為是否為1
if (extension > 0) {
// 計(jì)算擴(kuò)展頭的長度
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); // 負(fù)載數(shù)據(jù)
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;
}
}
//計(jì)算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案例就是小編分享給大家的全部內(nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
springcloud中RabbitMQ死信隊(duì)列與延遲交換機(jī)實(shí)現(xiàn)方法
死信隊(duì)列是消息隊(duì)列中非常重要的概念,同時(shí)我們需要業(yè)務(wù)場景中都需要延遲發(fā)送的概念,比如12306中的30分鐘后未支付訂單取消,那么本期,我們就來講解死信隊(duì)列,以及如何通過延遲交換機(jī)來實(shí)現(xiàn)延遲發(fā)送的需求,感興趣的朋友一起看看吧2022-05-05
Mybatis 自動(dòng)映射(使用需謹(jǐn)慎)
這篇文章主要介紹了Mybatis 自動(dòng)映射(使用需謹(jǐn)慎),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
SpringCloud Feign隔離與降級詳細(xì)分析
Feign是Netflix公司開發(fā)的一個(gè)聲明式的REST調(diào)用客戶端; Ribbon負(fù)載均衡、 Hystrⅸ服務(wù)熔斷是我們Spring Cloud中進(jìn)行微服務(wù)開發(fā)非常基礎(chǔ)的組件,在使用的過程中我們也發(fā)現(xiàn)它們一般都是同時(shí)出現(xiàn)的,而且配置也都非常相似2022-11-11
Spring Junit測試找不到SpringJUnit4ClassRunner.class的解決
這篇文章主要介紹了Spring Junit測試找不到SpringJUnit4ClassRunner.class的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04
Java的Struts2框架配合Ext JS處理JSON數(shù)據(jù)的使用示例
這篇文章主要介紹了Java的Struts2框架配合Ext JS處理JSON數(shù)據(jù)的使用示例,包括將Ext JS中的JSON數(shù)據(jù)解析為列表的方法,需要的朋友可以參考下2016-03-03
Java遞歸算法經(jīng)典實(shí)例(經(jīng)典兔子問題)
本文主要對經(jīng)典的兔子案例分析,來進(jìn)一步更好的理解和學(xué)習(xí)java遞歸算法,具有很好的參考價(jià)值,需要的朋友一起來看下吧2016-12-12
Spring?Boot實(shí)現(xiàn)微信掃碼登錄功能流程分析
這篇文章主要介紹了Spring?Boot?實(shí)現(xiàn)微信掃碼登錄功能,介紹了授權(quán)流程代碼和用戶登錄和登出的操作代碼,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-04-04
Java Elastic Job動(dòng)態(tài)添加任務(wù)實(shí)現(xiàn)過程解析
這篇文章主要介紹了Java Elastic Job動(dòng)態(tài)添加任務(wù)實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08

