Android視頻解碼及渲染

你們好,我是程序員kenney,今天給你們說說在android上如何作視頻解碼及渲染。 視頻解碼有多種方法,今天給你們介紹的是用android自帶的MediaCodec進行硬解碼,所謂硬解碼就是利用硬件進行解碼,速度快,與之相對就是軟解碼,速度慢,但兼容性好。 MediaCodec視頻解碼是基於生產者/消費者模式,裏面會有一些buffer,須要解碼一幀時從裏面拿出一個buffer,給buffer填充好數據,而後再送進去解碼,而後再拿出來解碼好的buffer,用完以後再還回去,以下圖所示:android

下面咱們來看看如何一步步實現視頻硬解碼及渲染:git

1. 建立一塊surface程序員

這個surface的做用是讓MediaCodec解碼到上面,若是你是用SurfaceView,那麼它自帶了一個surface,直接解碼到上面就會自動顯示出來,本文中由於還涉及到渲染,因此我是解碼到一個本身建立的surface上,這個surface又是經過surface texture建立的,而surface texture又是經過一個oes texture建立的,因此最終會解碼到一個紋理上,接下來就能夠用OpenGL進行渲染處理。github

2. 初始化MediaExtractor及MediaCodecide

MediaExtractor的做用是從視頻文件中提取數據,前面說的給buffer填充的數據就來源於此,初始化工做主要是給它設置視頻文件路徑,以及選擇軌道,本文講解的是視頻解碼,由於只關心視頻軌道,聲音就無論了。spa

mediaExtractor = MediaExtractor()
mediaExtractor.setDataSource(filePath)
val trackCount = mediaExtractor.getTrackCount()
for (i in 0 until trackCount) {
    val trackFormat = mediaExtractor.getTrackFormat(i)
    val mime = trackFormat.getString(MediaFormat.KEY_MIME)
    if (mime.contains("video")) {
        videoTrackIndex = i
        break
    }
}
if (videoTrackIndex == -1) {
    mediaExtractor.release()
    return
}
mediaExtractor.selectTrack(videoTrackIndex)
複製代碼

而後是初始化MediaCodec,能夠看到咱們會向它傳遞一個surface,初始化好以後,就讓它開始工做:code

mediaCodec = MediaCodec.createDecoderByType(videoMime)
mediaCodec.configure(videoFormat, surface, null, 0)
mediaCodec.start()
複製代碼

3. 讀取數據並解碼orm

這一步稍微複雜些,前面提到MediaCodec視頻解碼是基於生產者/消費者模式,咱們首先經過dequeueInputBuffer向它去要一個buffer用於承載要解碼的數據,能夠指定超時時間,由於裏面不必定有空閒buffer了:cdn

val inputBufferIndex = mediaCodec.dequeueInputBuffer(10000)
if (inputBufferIndex >= 0) {
	val buffer = mediaCodec.getInputBuffers()[inputBufferIndex]
}
複製代碼

而後從視頻文件中讀取數據,若是讀取到的數據長度小於0,說明已經讀完了,此時給buffer置一個標記BUFFER_FLAG_END_OF_STREAM,不然就是讀到了數據填充到了剛剛拿到的buffer,此時再將這個buffer經過queueInputBuffer送回MediaCodec,並讓MediaExtractor的讀取位置往前走:視頻

val sampleSize = mediaExtractor.readSampleData(buffer, 0)
if (sampleSize < 0) {
    mediaCodec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM)
    eos = true
} else {
    mediaCodec.queueInputBuffer(inputBufferIndex, 0, sampleSize, mediaExtractor.sampleTime, 0)
    mediaExtractor.advance()
}
複製代碼

接下來就是用dequeueOutputBuffer獲取解碼的結果,一樣也能夠設置超時間,若是獲取到的bufferBUFFER_FLAG_END_OF_STREAM標記,那說明解碼所有完成了:

val outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 10000)
if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
	return false
}
複製代碼

對於從dequeueOutputBuffer獲取到的結果,有一些是未解碼好的狀況,對於解碼好了的狀況,就經過releaseOutputBufferbuffer歸還回去:

when (outputBufferIndex) {
    MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED, 
    MediaCodec.INFO_OUTPUT_FORMAT_CHANGED, 
    MediaCodec.INFO_TRY_AGAIN_LATER -> {
    }
    else -> {
        mediaCodec.releaseOutputBuffer(outputBufferIndex, true)
        return true
    }
}
複製代碼

releaseOutputBuffer第二個參數若是傳true,就表示會渲染到surface上,此時用於構造這個surfacesurface texture就會收到onFrameAvailable()回調,這樣咱們就知道一幀解碼好了,這時調用surface textureupdateTexImage()方法將解碼數據更新到texture上,有了這個texture,就能夠用OpenGL作渲染了,渲染方法和以前的OpenGL教程裏是同樣的,使用完了記得將MediaCodec中止及釋放相關資源。

具體能夠看個人樣例代碼:github.com/kenneycode/…

感謝閱讀!

相關文章
相關標籤/搜索