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; //图像参数集PPS(Picture 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 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 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; } }