Android视频解码及渲染
今天给大家说说在android
上如何做视频解码及渲染。 视频解码有多种方法,今天给大家介绍的是用android
自带的MediaCodec
进行硬解码,所谓硬解码就是利用硬件进行解码,速度快,与之相对就是软解码,速度慢,但兼容性好。 MediaCodec
视频解码是基于生产者/消费者模式,里面会有一些buffer
,需要解码一帧时从里面拿出一个buffer
,给buffer
填充好数据,然后再送进去解码,然后再拿出来解码好的buffer
,用完之后再还回去,如下图所示:
下面我们来看看如何一步步实现视频硬解码及渲染:
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
停止及释放相关资源。
最后
如果你看到了这里,觉得文章写得不错就给个赞呗!欢迎大家评论讨论!如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足,定期免费分享技术干货。谢谢!
更多相关文章
- “罗永浩抖音首秀”销售数据的可视化大屏是怎么做出来的呢?
- Nginx系列教程(三)| 一文带你读懂Nginx的负载均衡
- 不吹不黑!GitHub 上帮助人们学习编码的 12 个资源,错过血亏...
- Android(安卓)RxJava 实际应用讲解:合并数据源 & 同时展示
- [Android] 移动端并行转码改造:音视频分离与视频分片
- 整理一下Android的面试题
- 一种Android客户端架构设计分享
- Android(安卓)播放视频(四)VideoView全屏视频播放
- Android(安卓)Gson使用入门及GsonFormat插件的使用