你們好,我是程序員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
獲取解碼的結果,一樣也能夠設置超時間,若是獲取到的buffer
有BUFFER_FLAG_END_OF_STREAM
標記,那說明解碼所有完成了:
val outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 10000)
if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
return false
}
複製代碼
對於從dequeueOutputBuffer
獲取到的結果,有一些是未解碼好的狀況,對於解碼好了的狀況,就經過releaseOutputBuffer
將buffer
歸還回去:
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
上,此時用於構造這個surface
的surface texture
就會收到onFrameAvailable()
回調,這樣咱們就知道一幀解碼好了,這時調用surface texture
的updateTexImage()
方法將解碼數據更新到texture
上,有了這個texture
,就能夠用OpenGL
作渲染了,渲染方法和以前的OpenGL
教程裏是同樣的,使用完了記得將MediaCodec
中止及釋放相關資源。
具體能夠看個人樣例代碼:github.com/kenneycode/…
感謝閱讀!