作者:程序员kenney

链接:https://juejin.im/post/5d7b81176fb9a06ae94d5fbf

大家好,今天给大家说说在android上如何做视频解码及渲染。 视频解码有多种方法,今天给大家介绍的是用android自带的MediaCodec进行硬解码,所谓硬解码就是利用硬件进行解码,速度快,与之相对就是软解码,速度慢,但兼容性好。 MediaCodec视频解码是基于生产者/消费者模式,里面会有一些buffer,需要解码一帧时从里面拿出一个buffer,给buffer填充好数据,然后再送进去解码,然后再拿出来解码好的buffer,用完之后再还回去,如下图所示:

android加载速度优化(Android解码及渲染)(1)

下面我们来看看如何一步步实现视频硬解码及渲染:

1. 创建一块surface

这个surface的作用是让MediaCodec解码到上面,如果你是用SurfaceView,那么它自带了一个surface,直接解码到上面就会自动显示出来,本文中因为还涉及到渲染,所以我是解码到一个自己创建的surface上,这个surface又是通过surface texture创建的,而surface texture又是通过一个oes texture创建的,所以最终会解码到一个纹理上,接下来就可以用OpenGL进行渲染处理。

2. 初始化MediaExtractor及MediaCodec

MediaExtractor的作用是从视频文件中提取数据,前面说的给buffer填充的数据就来源于此,初始化工作主要是给它设置视频文件路径,以及选择轨道,本文讲解的是视频解码,因为只关心视频轨道,声音就不管了。

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,初始化好之后,就让它开始工作:

mediaCodec = MediaCodec.createDecoderByType(videoMime) mediaCodec.configure(videoFormat, surface, null, 0) mediaCodec.start() 复制代码

3. 读取数据并解码

这一步稍微复杂些,前面提到MediaCodec视频解码是基于生产者/消费者模式,我们首先通过dequeueInputBuffer向它去要一个buffer用于承载要解码的数据,可以指定超时时间,因为里面不一定有空闲buffer了:

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

android加载速度优化(Android解码及渲染)(2)

最后

漫漫开发之路,我们只是其中的一小部分……只有不断的学习、进阶,才是我们的出路!才跟得上时代的进步!

我从事Android开发快十年了,今年年初我花两个月的时间收录整理了一套知识体系,此外,还有面试专题、架构视频,如果有想法深入的系统化的去学习的,可以关注我,我会把我收录整理的资料都送给大家,帮助大家更快的提升。

私信【架构】

关注 转发 私信,让更多需要的朋友们都可以看到并且领到!

android加载速度优化(Android解码及渲染)(3)

android加载速度优化(Android解码及渲染)(4)

,