你們好,我是程序員kenney
,今天給你們說說在android
上如何作視頻編碼。 所謂視頻編碼就是將每幀的圖片內容經過某種方式編碼成視頻,今天給你們介紹的是用android
自帶的MediaCodec
進行硬編碼,與前一篇文章的硬解碼相似,硬編碼就是利用硬件進行編碼。android
下面咱們就來看看如何一步步實現視頻硬編碼:git
1. 建立並配置MediaCodec程序員
private val MIME_TYPE = "video/avc"
...
val format = MediaFormat.createVideoFormat("video/avc", width, height).apply {
setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
setInteger(MediaFormat.KEY_BIT_RATE, 5120000)
setInteger(MediaFormat.KEY_FRAME_RATE, 25)
setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
}
mediaCodec = MediaCodec.createEncoderByType(MIME_TYPE)
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
mediaCodec.start()
複製代碼
這裏咱們經過將幀渲染到surface
上的方式向MediaCodec
提供編碼數據,所以KEY_COLOR_FORMAT
是COLOR_FormatSurface
,另外碼率、幀率及關鍵幀間隔可根據須要設置。github
2. 建立EGLapp
什麼是EGL
?關於EGL
的概念能夠參考個人一篇文章《OpenGL ES 高級進階:EGL及GL線程》,這裏EGL
的做用主要是2個,一個是向MediaCodec
提供編碼幀是經過將幀內容渲染到一個EGL Surface
上,這個EGL Surface
須要經過MediaCodec
給出的input surface
來建立,另外一個做用是作texture
的共享,由於編碼一般會放到另外一個線程裏,和提供幀texture
的線程不是同一個線程。ide
egl = EncodeEGL(shareContext, mediaCodec.createInputSurface()).apply {
init()
makeCurrent()
}
複製代碼
而後將這個EGL
綁定到調用線程中,個人demo
是將編碼放到一個獨立的線程上,因此只makeCurrent
就能夠了,不須要以後再restore
回來。post
3. 建立MediaMuxer編碼
這個東西是用來進行視頻容器封裝的,編碼只是獲得了一堆視頻幀數據,那播放器怎麼知道怎樣去播這堆數據呢?這時就須要容器這個東西,這裏用的是mp4
:spa
mediaMuxer = MediaMuxer(filePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
複製代碼
4. 渲染編碼幀線程
這裏渲染編碼幀就是很簡單地渲染一個texture
,沒什麼好說的,而後還要設置幀時間戳,這樣播放器播的時候才知道什麼時間播哪幀,最後swapBuffers
完成渲染,這樣就向MediaCodec
提供了一個待編碼的幀。
encodeRenderer.drawFrame(texture)
egl.setTimestamp(timestamp)
egl.swapBuffers()
複製代碼
5. 獲取編碼好的幀並將數據寫入文件
這一步有點複雜,由於要處理的狀況比較多,這裏只把一些關鍵的步驟列出來。
首先dequeueOutputBuffer()
將編碼好的一幀的index
拿出來,能夠設置超時時間,若是在超時時間到達時還未獲取到編好的一幀,就會返回,能夠經過返回的值來判斷是否成功拿到了編碼好的幀index
。
獲得了index
就從encoderOutputBuffers
中取出編碼好的幀數據,而後經過MediaMuxer
將數據寫入文件。
最後把dequeue
出來的buffer
再歸還回去。
val ret = mediaCodec.dequeueOutputBuffer(bufferInfo, 0)
...
encoderOutputBuffers = mediaCodec.outputBuffers
...
val encodedData = encoderOutputBuffers[ret]
...
encodedData.position(bufferInfo.offset)
encodedData.limit(bufferInfo.offset + bufferInfo.size)
mediaMuxer.writeSampleData(trackIndex, encodedData, bufferInfo)
mediaCodec.releaseOutputBuffer(ret, false)
複製代碼
如今來看一下demo
:
Thread {
val egl = EGL().apply {
init()
bind()
}
val bitmap = decodeBitmapFromAssets("test.png")
Thread {
val videoEncoder = VideoEncoder()
videoEncoder.init("/sdcard/test.mp4", 540, 540, egl.eglContext)
for (i in 0 until 100) {
val texture = GLUtil.bitmap2Texture(rotateBitmap(bitmap, i * 2f))
videoEncoder.encodeFrame(texture, 100 * i * 1000000L)
GLUtil.deleteTexture(texture)
}
videoEncoder.encodeFrame(0, 0)
videoEncoder.release()
}.start()
}.start()
複製代碼
視頻編碼比較常見的使用場景是相機錄像和將一個視頻生成帶特效的視頻,簡單起見,這裏我把一張圖旋轉成不的角度來作爲視頻的幀,編碼出一個540*540
的mp4
視頻,幀間隔爲100ms
,編好的視頻就能夠用播放器來播放了,效果是這樣的:
代碼在個人github
上:github.com/kenneycode/…
感謝閱讀!