Android 用MediaCodec實現視頻硬解碼(轉)

來源:互聯網
上載者:User

標籤:android   style   class   blog   code   java   

本文向你講述如何用android標準的API (MediaCodec)實現視頻的硬體編解碼。常式將從網路攝影機採集視頻開始,然後進行H264編碼,再解碼,然後顯示。我將盡量講得簡短而清晰,不展示 那些不相關的代碼。但是,我不建議你讀這篇文章,也不建議你開發這類應用,而應該轉而開發一些戳魚、打鳥、其樂融融的程式。好吧,下面的內容是寫給那些執 迷不悟的人的,看完之後也許你會同意我的說法:Android只是一個玩具,很難指望它來做靠譜的應用。

1、從網路攝影機採集視頻

      可以通過網路攝影機Preview的回調,來擷取視頻資料。

      首先建立網路攝影機,並設定參數:


                cam = Camera.open();            cam.setPreviewDisplay(holder);                                Camera.Parameters parameters = cam.getParameters();            parameters.setFlashMode("off"); // 無閃光燈            parameters.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);            parameters.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO);            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);             parameters.setPreviewFormat(ImageFormat.YV12);                 parameters.setPictureSize(camWidth, camHeight);            parameters.setPreviewSize(camWidth, camHeight);            //這兩個屬性 如果這兩個屬性設定的和真實手機的不一樣時,就會報錯            cam.setParameters(parameters);

 

       

寬度和高度必須是網路攝影機支援的尺寸,否則會報錯。要獲得所有支援的尺寸,可用getSupportedPreviewSizes,這裡不再累述。據說所有的參數必須設全,漏掉一個就可能報錯,不過只是據說,我只設了幾個屬性也沒出錯。    然後就開始Preview了:

            buf = new byte[camWidth * camHeight * 3 / 2];            cam.addCallbackBuffer(buf);            cam.setPreviewCallbackWithBuffer(this);                        cam.startPreview(); 

 

  setPreviewCallbackWithBuffer是很有必要的,不然每次回調系統都重新分配緩衝區,效率會很低。

    在onPreviewFrame中就可以獲得原始的圖片了(當然,this 肯定要 implements PreviewCallback了)。這裡我們是把它傳給編碼器:

    public void onPreviewFrame(byte[] data, Camera camera) {        if (frameListener != null) {            frameListener.onFrame(data, 0, data.length, 0);        }        cam.addCallbackBuffer(buf);    }

 

2、編碼

    首先要初始化編碼器:

           mediaCodec = MediaCodec.createEncoderByType("Video/AVC");        MediaFormat mediaFormat = MediaFormat.createVideoFormat(type, width, height);        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000);        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);        mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);        mediaCodec.start();

 


    然後就是給他喂資料了,這裡的資料是來自網路攝影機的:

 public void onFrame(byte[] buf, int offset, int length, int flag) {    ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();    ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();    int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);    if (inputBufferIndex >= 0)        ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];        inputBuffer.clear();        inputBuffer.put(buf, offset, length);        mediaCodec.queueInputBuffer(inputBufferIndex, 0, length, 0, 0);    }    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();    int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo,0);    while (outputBufferIndex >= 0) {        ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];        if (frameListener != null)            frameListener.onFrame(outputBuffer, 0, length, flag);        mediaCodec.releaseOutputBuffer(outputBufferIndex, false);        outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);    } 

 

先把來自網路攝影機的資料餵給它,然後從它裡面取壓縮好的資料餵給解碼器。

3、解碼和顯示

     首先初始化解碼器:

            mediaCodec = MediaCodec.createDecoderByType("Video/AVC");            MediaFormat mediaFormat = MediaFormat.createVideoFormat(mime, width, height);            mediaCodec.configure(mediaFormat, surface, null, 0);            mediaCodec.start();

 

             這裡通過給解碼器一個surface,解碼器就能直接顯示畫面。

     然後就是處理資料了:

    public void onFrame(byte[] buf, int offset, int length, int flag) {            ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();                int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);            if (inputBufferIndex >= 0) {                ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];                inputBuffer.clear();                inputBuffer.put(buf, offset, length);                mediaCodec.queueInputBuffer(inputBufferIndex, 0, length, mCount * 1000000 / FRAME_RATE, 0);                    mCount++;            }        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();        int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo,0);        while (outputBufferIndex >= 0) {            mediaCodec.releaseOutputBuffer(outputBufferIndex, true);            outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);        }    }

 

        queueInputBuffer第三個參數是時間戳記,其實怎麼寫都無所謂,只要是按時間軸性增加的就可以,這裡就隨便弄一個了。後面一段的代碼就是把緩 沖區給釋放掉,因為我們直接讓解碼器顯示,就不需要解碼出來的資料了,但是必須要這麼釋放一下,否則解碼器始終給你留著,記憶體就該不夠用了。


好了,到現在,基本上就可以了。如果你運氣夠好,現在就能看到視頻了,比如在我的三星手機上這樣就可以了。但是,我試過幾個其他平台,多數都不可以,總是有各種各樣的問題,如果要開發一個不依賴平台的應用,還有很多的問題要解決。說說我遇到的一些情況:


1、視頻尺寸

     一般都能支援176X144/352X288這種尺寸,但是大一些的,640X480就有很多機子不行了,至於為什麼,我也不知道。當然,這個尺寸必須和網路攝影機預覽的尺寸一致,預覽的尺寸可以枚舉一下。

2、色彩空間

    根據ANdroid SDK文檔,確保所有硬體平台都支援的顏色,在網路攝影機預覽輸出是YUV12,在編碼器輸入是COLOR_FormatYUV420Planar,也就是前面代碼中設定的那樣。       不過,文檔終究是文檔,否則安卓就不是安卓。

    在有的平台上,這兩個顏色格式是一樣的,網路攝影機的輸出可以直接作為編碼器的輸入。也有的平台,兩個是不一樣的,前者就是YUV12,後者等於I420,需要把前者的UV分量顛倒一下。下面的代碼效率不高,可供參考。

    byte[] i420bytes = null;    private byte[] swapYV12toI420(byte[] yv12bytes, int width, int height) {        if (i420bytes == null)            i420bytes = new byte[yv12bytes.length];        for (int i = 0; i < width*height; i++)            i420bytes[i] = yv12bytes[i];        for (int i = width*height; i < width*height + (width/2*height/2); i++)            i420bytes[i] = yv12bytes[i + (width/2*height/2)];        for (int i = width*height + (width/2*height/2); i < width*height + 2*(width/2*height/2); i++)            i420bytes[i] = yv12bytes[i - (width/2*height/2)];        return i420bytes;    }

 

      這裡的困難是,我不知道怎樣去判斷是否需要這個轉換。據說,Android 4.3不用再從網路攝影機的PreView裡面取映像,避開了這個問題。這裡有個例子,雖然我沒讀,但看起來挺厲害的樣子,應該不會有錯吧(覺厲應然)。http://bigflake.com/mediacodec/CameraToMpegTest.java.txt


3、輸入輸出緩衝區的格式

    SDK裡並沒有規定格式,但是,這種情況H264的格式基本上就是附錄B。但是,也有比較有特色的,它就是不帶那個StartCode,就是那個0x000001,搞得把他編碼器編出來的東西送給他的解碼器,他自己都解不出來。還好,我們可以自己加。

                ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];                byte[] outData = new byte[bufferInfo.size + 3];                    outputBuffer.get(outData, 3, bufferInfo.size);                if (frameListener != null) {                    if ((outData[3]==0 && outData[4]==0 && outData[5]==1)                    || (outData[3]==0 && outData[4]==0 && outData[5]==0 && outData[6]==1))                    {                        frameListener.onFrame(outData, 3, outData.length-3, bufferInfo.flags);                    }                    else                    {                        outData[0] = 0;                        outData[1] = 0;                        outData[2] = 1;                        frameListener.onFrame(outData, 0, outData.length, bufferInfo.flags);                    }                }

 


4、有時候會死在dequeueInputBuffer(-1)上面

根據SDK文檔,dequeueInputBuffer 的參數表示等待的時間(毫秒),-1表示一直等,0表示不等。按常理傳-1就行,但實際上在很多機子上會掛掉,沒辦法,還是傳0吧,丟幀總比掛掉好。當然也可以傳一個具體的毫秒數,不過沒什麼大意思吧。


在遇到上述的問題之後,我給出了我的感慨:Android是一個玩具。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.