Android音視頻--H.264視頻流解碼

1. 簡介

H.264是比較多開發者使用較多的一種數字視頻壓縮格式,主要用於直播流的傳輸與視頻網站的視頻流傳輸,也有很多開發者開始使用H.265進行視頻壓縮,性能較H.264提高較大。本篇文章着重介紹使用MediaCodec硬件H.264裸字節流數據的實現方式,有關於更多H.264的介紹能夠查看參考文章中H.264的結構介紹。html

2.使用MediaCodec硬解碼

2.1 MediaCodec介紹

  • MediaCodec類Android提供的用於訪問低層多媒體編/解碼器接口,它是Android低層多媒體架構的一部分,一般與MediaExtractor、MediaMuxer、AudioTrack結合使用,可以編解碼諸如H.26四、H.26五、AAC、3gp等常見的音視頻格式。java

  • Android 底層多媒體模塊採用的是 OpenMax 框架,任何 Android 底層編解碼模塊的實現,都必須遵循 OpenMax 標準。Google 官方默認提供了一系列的軟件編解碼器:包括:OMX.google.h264.encoder,OMX.google.h264.encoder, OMX.google.aac.encoder, OMX.google.aac.decoder 等等,而硬件編解碼功能,則須要由芯片廠商依照 OpenMax 框架標準來完成,因此,通常採用不一樣芯片型號的手機,硬件編解碼的實現和性能是不一樣的。android

  • Android 應用層統一由 MediaCodec API 來提供各類音視頻編解碼功能,由參數配置來決定採用何種編解碼算法、是否採用硬件編解碼加速等。git

2.2 MediaCodec 工做流程

編解碼器處理輸入數據併產生輸出數據,MediaCodec 使用輸入輸出緩存,異步處理數據。簡要地說,通常的處理步驟以下github

  • 請求一個空的輸入 input buffer
  • 填入數據、並將其交給 MediaCodec
  • MediaCodec 處理數據後,將處理後的數據放在一個空的 output buffer
  • 獲取填充數據了的 output buffer,獲得其中的數據,而後將其返還給 MediaCodec

2.3 MediaCodec API 說明

MediaCodec能夠處理具體的視頻流,主要有這幾個方法:算法

  • configure:配置爲編碼器start:成功地配置組件後,調用start方法。
  • getInputBuffers:獲取須要編碼數據的輸入流隊列,返回的是一個ByteBuffer數組
  • queueInputBuffer:輸入流入隊列dequeueInputBuffer:從輸入流隊列中取數據進行編碼操做
  • getOutputBuffers:獲取編解碼以後的數據輸出流隊列,返回的是一個ByteBuffer數組
  • dequeueOutputBuffer:從輸出隊列中取出編碼操做以後的數據
  • releaseOutputBuffer:處理完成,釋放ByteBuffer數據
  • stop:完成解碼/編碼任務後,需注意的是codec任然處於活躍狀態且準備從新start。
  • flush:沖洗組件的輸入和輸出端口release:釋放codec實例使用的資源。
  • reset:使codec返回到初始(未初始化)狀態。

2.4 Talk is cheap, Show me the code

初始化MediaCodec數組

/** * 視頻類型 */
    private final static String MIME_TYPE = "video/avc";

    /** * 初始化播放 */
    private void initVideo(SurfaceHolder holder) {
        try {
            // 初始化MediaCodec,方法有兩種,分別是經過名稱和類型來建立
            // 這裏使用經過類型來建立
            mMediaCodec = MediaCodec.createDecoderByType(MIME_TYPE);
            // 獲取視頻的寬高
            mVideoHeight = holder.getSurfaceFrame().width();
            mVideoWidth = holder.getSurfaceFrame().height();
            // MediaFormat,這個類包含了比特率、幀率、關鍵幀間隔時間等,其中比特率若是過低就會形成相似馬賽克的現象。
            mMediaFormat = MediaFormat.createVideoFormat(MIME_TYPE,
                    1080, 1920);
            // 設置比特率 
            mMediaFormat.setInteger(KEY_BIT_RATE,
                    mVideoHeight * mVideoWidth * 5);
            // 設置幀率 
            mMediaFormat.setInteger(KEY_FRAME_RATE, 30);
            
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                // 描述編碼器要使用的所需比特率模式的鍵
                // BITRATE_MODE_CQ: 表示徹底不控制碼率,盡最大可能保證圖像質量
                //BITRATE_MODE_CBR: 表示編碼器會盡可能把輸出碼率控制爲設定值
                //BITRATE_MODE_VBR: 表示編碼器會根據圖像內容的複雜度(其實是幀間變化量的大小)來動態調整輸出碼率,圖像複雜則碼率高,圖像簡單則碼率低;
                mMediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE,
                        MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
            }
            mMediaFormat.setInteger(KEY_I_FRAME_INTERVAL, 1);

            byte[] headerSps = {0, 0, 0, 1, 103, 66, 0, 41, -115, -115, 64, 80,
                    30, -48, 15, 8, -124, 83, -128};
            byte[] headerPps = {0, 0, 0, 1, 104, -54, 67, -56};

            mMediaFormat.setByteBuffer("csd-0", ByteBuffer.wrap(headerSps));
            mMediaFormat.setByteBuffer("csd-1", ByteBuffer.wrap(headerPps));

            mMediaCodec.configure(mMediaFormat, holder.getSurface(), null, 0);
            mMediaCodec.start();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
複製代碼

視頻解碼部分代碼緩存

將接收到或從文件讀取到的byte[]傳入onFrame中架構

/** * 解碼數據並顯示視頻 * buf 視頻數據組 * offset 數據偏移量 * length 有效長度 */
    private void onFrame(byte[] buf, int offset, int length) {
        try {
            ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
            int inputBufferIndex = mMediaCodec.dequeueInputBuffer(0);
                if (inputBufferIndex >= 0) {
                    ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                    inputBuffer.clear();
                    inputBuffer.put(buf, offset, length);
                    mMediaCodec.queueInputBuffer(inputBufferIndex, 0, length, mCount
                            * 30, 0);
                    mCount++;
                }
            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
            int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0);
            while (outputBufferIndex >= 0) {
                mMediaCodec.releaseOutputBuffer(outputBufferIndex, true);
                outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0);
                if (!isPlayingSound) {
                        mHandler.postDelayed(() -> isPlayingSound = true, 1000);
                }
            }
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
複製代碼

3. 使用FFmpeg進行解碼

使用與原理能夠瀏覽參考文章3。 具體代碼實現方式可參考此類app

4. 參考文章

  1. Android MediaCodec 官方文檔介紹
  2. Android原生編解碼接口 MediaCodec 之——徹底解析
  3. FFmpeg解碼H.264
  4. H.264結構
相關文章
相關標籤/搜索