AVDataProcess/H264Parser.java

335 lines
12 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Formatter;
public class H264Parser {
//0x00000001或0x000001是一个nalu的起始标志遇到下一个此标志时为该nalu的结尾。
//起始标志的后面第一个字节type里包含有nalu的类型type & 0x1F即为该nalu的类型nalu_type
private static final int NALU_TYPE_SLICE = 1; //非IDR图像中不采用数据划分的片段为P或B帧
private static final int NALU_TYPE_DPA = 2; //非IDR图像中A类数据划分片段
private static final int NALU_TYPE_DPB = 3; //非IDR图像中B类数据划分片段
private static final int NALU_TYPE_DPC = 4; //非IDR图像中C类数据划分片段
private static final int NALU_TYPE_IDR = 5; //IDR图像的片段关键帧I帧
private static final int NALU_TYPE_SEI = 6; //补充增强信息
private static final int NALU_TYPE_SPS = 7; //序列参数集Sequence Parameter Set
private static final int NALU_TYPE_PPS = 8; //图像参数集PPSPicture Parameter Set
private static final int NALU_TYPE_AUD = 9; //分割符
private static final int NALU_TYPE_EOSEQ = 10; //序列结束符
private static final int NALU_TYPE_EOSTREAM = 11; //流结束符
private static final int NALU_TYPE_FILL = 12; //填充数据
private static final int NALU_PRIORITY_DISPOSABLE = 0;
private static final int NALU_PRIORITY_LOW = 1;
private static final int NALU_PRIORITY_HIGH = 2;
private static final int NALU_PRIORITY_HIGHEST = 3;
private boolean mFirstFind = true; //第一次查找起始码
private int mStartCodeLen = 0; //起始码长度
private int mCurStartCodeLen = 0;
private int mFrameLen = 0; //帧长度
private int mFrameFirstByte = 0; //帧数据中的第一个字节
private int mCurFrameFirstByte = 0;
private int mPos = 0;
private int mLen = 0;
private String mStartCode = "";
private int mNaluType = 0;
private int mForbiddenBit = 0;
private int mNalReferenceIdc = 0;
private byte[] mFrameBuffer = null;
private FileOutputStream mFileOutputStream = null;
private boolean mIsAddFrameLen = false;
public static void main(String[] args) {
H264Parser h264Parser = null;
if (args.length == 2) {
h264Parser = new H264Parser();
if ("1".equals(args[1])) {
h264Parser.mIsAddFrameLen = true;
}
h264Parser.init();
h264Parser.ParseH264(args[0]);
} else if (args.length == 1) {
h264Parser = new H264Parser();
h264Parser.init();
h264Parser.ParseH264(args[0]);
} else {
System.out.println("Missing h264 filename.");
String path = "C:\\Users\\hw\\Desktop\\test.h264";
h264Parser = new H264Parser();
h264Parser.init();
h264Parser.ParseH264(path);
}
if (h264Parser != null) {
h264Parser.clear();
}
}
public H264Parser() {
}
public void init() {
mFrameBuffer = new byte[2 * 1024 * 1024];
if (mIsAddFrameLen) {
File file = new File("test_2.h264");
try {
mFileOutputStream = new FileOutputStream(file);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void clear() {
if (mFileOutputStream != null) {
try {
mFileOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//如果NALU对应的Slice为一帧的开始就用0x00000001(即4个字节长度)否则就用0x000001(即3个字节长度)。
public int FindStartCode(InputStream inputStream) {
byte[] startCode = new byte[4];
int len = -1;
try {
len = inputStream.read(startCode);
} catch (Exception e) {
e.printStackTrace();
}
if (len == 3) {
if (startCode[0]==0 && startCode[1]==0 && startCode[2]==1) {
len = 3;
} else {
len = 0;
}
} else if (len == 4) {
if (startCode[0]==0 && startCode[1]==0 && startCode[2]==0 && startCode[3]==1) {
len = 4;
} else {
len = 0;
}
}
return len;
}
public int FindStartCode(List<Integer> startCodeList) {
int len = startCodeList.size();
if (len == 3) {
if (startCodeList.get(0)==0 && startCodeList.get(1)==0 && startCodeList.get(2)==1) {
return 3;
}
} else if (len == 4) {
if (startCodeList.get(0)==0 && startCodeList.get(1)==0 && startCodeList.get(2)==0 && startCodeList.get(3)==1) {
return 4;
} else if (startCodeList.get(0)==0 && startCodeList.get(1)==0 && startCodeList.get(2)==1) {
return 3;
}
}
return 0;
}
public int GetNalu(InputStream inputStream) {
mPos = mPos + mLen;
//第一次查找起始码,只是为了移动文件指针
if (mFirstFind) {
mFirstFind = false;
if (-1 == (mStartCodeLen = FindStartCode(inputStream))) {
return -1;
}
}
if (mStartCodeLen == 3) {
mStartCode = "001";
// 添加起始码
mFrameBuffer[0] = 0;
mFrameBuffer[1] = 0;
mFrameBuffer[2] = 1;
} else {
mStartCode = "0001";
// 添加起始码
mFrameBuffer[0] = 0;
mFrameBuffer[1] = 0;
mFrameBuffer[2] = 0;
mFrameBuffer[3] = 1;
}
List<Integer> startCodeList = new ArrayList<>();
int frameLen = 0;
while(true) {
int tmp = -1;
//一个字节一个字节从文件流中读取
try {
tmp = inputStream.read();
} catch (Exception e) {
e.printStackTrace();
}
//读取到文件尾
if (tmp == -1) {
mFrameLen = frameLen;
// 另存为
saveAs(frameLen);
return -1;
}
//读取到的数据保存到buffer
mFrameBuffer[mStartCodeLen+frameLen] = (byte)(0xff & tmp);
//帧数据长度累加
frameLen++;
//将从文件中读取的数据保存到list中
startCodeList.add(tmp);
//从一帧的第一个字节中获取NALU信息
if (frameLen == 1) {
//如果上一次查找到的起始码长度是3则上次已经保存了第一个字节的数据
if (mStartCodeLen == 3) {
tmp = mFrameFirstByte;
//读取到的数据保存到buffer
mFrameBuffer[mStartCodeLen+frameLen] = (byte)(0xff & tmp);
frameLen++; //帧长度加上第一个字节
}
mCurFrameFirstByte = tmp;
mForbiddenBit = tmp & 0x80;
mNalReferenceIdc = tmp & 0x60;
mNaluType = tmp & 0x1f;
}
//保存四个字节到list中
if (startCodeList.size() < 4) {
continue;
}
//查找起始码如果返回值大于0就表示找到了
int startCodeLen = FindStartCode(startCodeList);
if (startCodeLen > 0) {
if (startCodeLen == 3) {
mFrameFirstByte = tmp;
}
frameLen = frameLen - 4;
mLen = frameLen + mStartCodeLen;
mStartCodeLen = startCodeLen;
break;
}
startCodeList.remove(0);
}
mFrameLen = frameLen;
// 另存为
saveAs(frameLen);
return 0;
}
/**
* 添加帧长度并另存为
*
* @param frameLen 除去起始码的帧长度
*/
public void saveAs(int frameLen) {
if (mFileOutputStream != null) {
try {
//System.out.println("len = " + mStartCodeLen+frameLen);
mFileOutputStream.write(intToBytes(mStartCodeLen+frameLen), 0, 4);
mFileOutputStream.write(mFrameBuffer, 0, mStartCodeLen+frameLen);
} catch (Exception e) {
e.printStackTrace();
}
mFrameBuffer[0] = '\0';
}
}
public boolean ParseH264(String fileName) {
if (fileName.equals("")) {
return false;
}
InputStream inputStream = null;
try {
File file = new File(fileName);
inputStream = new FileInputStream(file);
} catch (Exception e) {
e.printStackTrace();
inputStream = null;
}
if (inputStream == null) {
return false;
}
System.out.println("-----+---------+----- NALU Table -+---------+------------+");
System.out.println(" NUM | POS | IDC | TYPE | LEN | START_CODE |");
System.out.println("-----+---------+---------+--------+---------+------------+");
int iNaluNum = 0;
while(true) {
int dataLen = GetNalu(inputStream);
iNaluNum++;
String sNaluType = "";
switch(mNaluType) {
case NALU_TYPE_SLICE: sNaluType = "SLICE"; break;
case NALU_TYPE_DPA: sNaluType = "DPA"; break;
case NALU_TYPE_DPB: sNaluType = "DPB"; break;
case NALU_TYPE_DPC: sNaluType = "DPC"; break;
case NALU_TYPE_IDR: sNaluType = "IDR"; break;
case NALU_TYPE_SEI: sNaluType = "SEI"; break;
case NALU_TYPE_SPS: sNaluType = "SPS"; break;
case NALU_TYPE_PPS: sNaluType = "PPS"; break;
case NALU_TYPE_AUD: sNaluType = "AUD"; break;
case NALU_TYPE_EOSEQ: sNaluType = "EOSEQ"; break;
case NALU_TYPE_EOSTREAM: sNaluType = "EOSTREAM"; break;
case NALU_TYPE_FILL: sNaluType = "FILL"; break;
}
String sIdc = "";
switch(mNalReferenceIdc >> 5) {
case NALU_PRIORITY_DISPOSABLE:sIdc = "DISPOS"; break;
case NALU_PRIORITY_LOW: sIdc = "LOW"; break;
case NALU_PRIORITY_HIGH: sIdc = "HIGH"; break;
case NALU_PRIORITY_HIGHEST: sIdc = "HIGHEST";break;
}
Formatter formatter = new Formatter(System.out);
formatter.format("%5d| %8d| %8s| %7s| %8d| %11s|\n", iNaluNum, mPos, sIdc, sNaluType, mFrameLen, mStartCode);
if (dataLen == -1) {
break;
}
if (iNaluNum == 3) {
//break;
}
}
try {
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
/**
* int型转四字节byte数组
*/
public static byte[] intToBytes(int i){
byte[] result=new byte[4];
result[3]=(byte)((i >> 24)& 0xFF);
result[2]=(byte)((i >> 16)& 0xFF);
result[1]=(byte)((i >> 8)& 0xFF);
result[0]=(byte)(i & 0xFF);
return result;
}
}