一、爲什麼要進行視頻幀的分割?
通常與視頻服務器進行數據請求的時候,服務器方面發過來的都是已經分割好的NAL單元數據。但是當我們沒有視頻服務器支持,也需要調試一下程序,視頻源爲本地文件,這個時候我們就需要用到NAL單元的分割了,不然你沒辦法扔給解碼器。
二、H264的基礎知識
1、 NAL全稱Network Abstract Layer, 即網絡抽象層。
在H.264/AVC視頻編碼標準中,整個系統框架被分爲了兩個層面:視頻編碼層面(VCL)和網絡抽象層面(NAL)。其中,前者負責有效表示視頻數據的內容,而後者則負責格式化數據並提供頭信息,以保證數據適合各種信道和存儲介質上的傳輸。因此我們平時的每幀數據就是一個NAL單元(SPS與PPS除外)。在實際的H264數據幀中,往往幀前面帶有00 00 00 01 或 00 00 01分隔符,一般來說編碼器編出的首幀數據爲PPS與SPS,接着爲I幀……
2、如何判斷幀類型(是圖像參考幀還是I、P幀等)?
我們還是接着看最上面圖的碼流對應的數據來層層分析,以00 00 00 01分割之後的下一個字節就是NALU類型,將其轉爲二進制數據後,解讀順序爲從左往右算,如下:
(1)第1位禁止位,值爲1表示語法出錯
(2)第2~3位爲參考級別
(3)第4~8爲是nal單元類型
計算公式爲:比如0x65 & 0x1F = 5
例如上面00000001後有67,68以及65,41
其中0x67的二進制碼爲:
0110 0111
4-8爲00111,轉爲十進制7,參考第二幅圖:7對應序列參數集SPS
其中0x68的二進制碼爲:
0110 1000
4-8爲01000,轉爲十進制8,參考第二幅圖:8對應圖像參數集PPS
其中0x65的二進制碼爲:
0110 0101
4-8爲00101,轉爲十進制5,參考第二幅圖:5對應IDR圖像中的片(I幀)
其中0x41的二進制碼爲:
0100 0001
4-8爲00001,轉爲十進制1,參考第二幅圖:根據上圖可知道這段碼流是【不分區、非IDR圖像的片】,在baseline的檔次中就是P幀,因爲baseline沒有B幀。
所以判斷是否爲I幀的算法爲: (NALU類型 & 0001 1111) = 5 即 NALU類型 & 31 = 5
比如0x65 & 31 = 5
3.以上區分不出B幀和P幀
三、實現方案
四、實現代碼
/**
* 描述:用來將本地h264數據分割成一幀一幀的數據
* 作者:chezi008 on 2017/6/29 16:50
* 郵箱:[email protected]
*/
public class H264ReadRunable implements Runnable {
private static final int READ_BUFFER_SIZE = 1024 * 5;
private static final int BUFFER_SIZE = 1024 * 1024;
private String TAG = getClass().getSimpleName();
private H264ReadListener h264ReadListener;
private DataInputStream mInputStream;
public void setH264ReadListener(H264ReadListener h264ReadListener) {
this.h264ReadListener = h264ReadListener;
}
private byte[] buffer;
@Override
public void run() {
try {
Log.d(TAG, "run: " + FileConstant.h264FilePath);
mInputStream = new DataInputStream(new FileInputStream(FileConstant.h264FilePath));
buffer = new byte[BUFFER_SIZE];
int readLength;
int naluIndex = 0;
int bufferLength = 0;
while ((readLength = mInputStream.read(buffer, bufferLength , READ_BUFFER_SIZE)) > 0) {
bufferLength += readLength;
for (int i = 5; i < bufferLength - 4; i++) {
if (buffer[i] == 0x00 &&
buffer[i + 1] == 0x00 &&
buffer[i + 2] == 0x00 &&
buffer[i + 3] == 0x01) {
naluIndex = i;
// Log.d(TAG, "run: naluIndex:"+naluIndex);
byte[] naluBuffer = new byte[naluIndex];
System.arraycopy(buffer,0,naluBuffer,0,naluIndex);
h264ReadListener.onFrameData(naluBuffer);
bufferLength -=naluIndex;
System.arraycopy(buffer,naluIndex,buffer,0,bufferLength );
i = 5;
Thread.sleep(30);
}
}
}
h264ReadListener.onStopRead();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public interface H264ReadListener {
void onFrameData(byte[] datas);
void onStopRead();
}
}
五、參考與引用
參考:h264基礎知識:http://blog.csdn.net/dittychen/article/details/55509718