Android H264幀切割

一、爲什麼要進行視頻幀的分割?

通常與視頻服務器進行數據請求的時候,服務器方面發過來的都是已經分割好的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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章