1. 程式人生 > >Android利用mediacodec進行視訊H264編碼解碼播放

Android利用mediacodec進行視訊H264編碼解碼播放

H264是目前最常用的視訊壓縮格式之一,可以將視訊、圖片、音訊等轉換為字串流形式,以此可以進行再次編輯、傳輸等。詳情參考http://blog.csdn.net/lcalqf/article/details/42556219

Android裡,最常用的視訊編碼解碼用的API就是mediacodec了,可以進行多種格式的硬解碼,也能和mediamuxer一起使用實現音視訊檔案的編輯(結合MediaExtractor),用OpenGL繪製Surface並生成mp4檔案,螢幕錄影以及類似Camera app裡的錄影功能(雖然這個用MediaRecorder更合適)等注意它們和其它一些多媒體相關類的關係和區別:MediaExtractor

用於音視訊分路,和MediaMuxer正好是反過程。MediaFormat用於描述多媒體資料的格式。MediaRecorder用於錄影+壓縮編碼,生成編碼好的檔案如mp4, 3gpp,視訊主要是用於錄製Camera previewMediaPlayer用於播放壓縮編碼後的音視訊檔案。AudioRecord用於錄製PCM資料。AudioTrack用於播放PCM資料。PCM即原始音訊取樣資料,可以用如vlc播放器播放。參考部落格:http://www.thinksaas.cn/topics/0/348/348569.html

好了,然後開始我們的編解碼之旅吧。

首先,在確定了輸入源以後(我的是mSurface,裡面是儲存著我的截圖Surface

),設定編碼器:

MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);//MIME_TYPE = "video/avc",H264的MIME型別,寬,高
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);//設定顏色格式
format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);//設定位元率
format.setInteger(MediaFormat
.KEY_FRAME_RATE, FRAME_RATE);//設定幀率 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);//設定關鍵幀 mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);//建立編碼器 mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);//四個引數,第一個是media格式,第二個是解碼器播放的surfaceview,第三個是MediaCrypto,第四個是編碼解碼的標識 mSurface = mEncoder.createInputSurface();//我的輸入源 Log.d(TAG, "created input surface: " + mSurface); mEncoder.start();

附上我輸入源的程式碼吧,

mMediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
Intent captureIntent = mMediaProjectionManager.createScreenCaptureIntent();
startActivityForResult(captureIntent, REQUEST_CODE);
MediaProjection mediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
mVirtualDisplay = mMediaProjection.createVirtualDisplay(TAG + "-display",
mWidth, mHeight, mDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
mSurface, null, null);

從我程式碼中一行行拷過來的,有點散,不過基本意思就是用mediaprojectionmanager截圖,獲取資料。

然後是進行資料編碼:

inputObject=new InputObject();
int index = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_US);//獲取輸出區的緩衝的索引
Log.i(TAG, "dequeue output buffer index=" + index);
if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    resetOutputFormat();//重新設定media格式
} else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
    Log.d(TAG, "retrieving buffers time out!");
    try {
        // wait 10ms
Thread.sleep(10);
} catch (InterruptedException e) {
    }
} else if (index >= 0) {
    encodeToVideoTrack(index);
mEncoder.releaseOutputBuffer(index, false);//釋放快取的資源

對於重新設定media格式這裡,我並沒進行更多操作:

private void resetOutputFormat() {
    // should happen before receiving buffers, and should only happen once
MediaFormat newFormat = mEncoder.getOutputFormat();
}

接下來根據索引就是獲取編碼的資料(我這裡將它取到了byte陣列中):

private void encodeToVideoTrack(int index) {
    ByteBuffer encodedData = mEncoder.getOutputBuffer(index);
    if (encodedData != null) {
        encodedData.position(mBufferInfo.offset);
encodedData.limit(mBufferInfo.offset + mBufferInfo.size);
        try
{
            int jj=encodedData.remaining();
            byte[] b=new byte[encodedData.remaining()];
encodedData.get(b, 0, b.length);}
        catch(Exception e){
            e.printStackTrace();
}
    }
}

然後就是進行解碼和播放:

首先是初始化解碼器:

MediaFormat mediaFormat = MediaFormat.createVideoFormat(
        MIME_TYPE, mWidth, mHeigh);
mediaCodec = MediaCodec.createDecoderByType(MIME_TYPE);//這裡是建立的解碼器
mediaCodec.configure(mediaFormat, surface, null, 0);//注意上面編碼器的註釋,看看區別
mediaCodec.start();

然後是解碼:

int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);//獲取輸入緩衝區的索引
if (inputBufferIndex >= 0) {
    ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(byteBuffer);//先獲取緩衝區,再放入值
mediaCodec.queueInputBuffer(inputBufferIndex, 0, size, i * 1000000 / 30, 0);//四個引數,第一個是輸入緩衝區的索引,第二個是放入的資料大小,第三個是時間戳,保證遞增就是
i++;
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
while (outputBufferIndex >= 0) {
    mediaCodec.releaseOutputBuffer(outputBufferIndex, true);//釋放緩衝區解碼的資料到surfaceview,一般到了這一步,surfaceview上就有畫面了
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}

步驟就是這麼簡單,但是實際執行會有很多BUG,並且感覺和不同的裝置型號也有關係。

附上我做這個時候的幾個參考地址(都是大神啊)

http://www.cnblogs.com/Xiegg/p/3428529.html(mediacodec的詳細文件翻譯)

http://blog.csdn.net/angcyo/article/details/51043367(mediacodec混合製作MP4)

http://blog.csdn.net/guojin08/article/details/27555473(mediacodec實現硬編碼)