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/…

感谢阅读!

相关文章
相关标签/搜索