背景:
随着多媒体产业的发展,手机端对视频解码性能要求越来越高。如果采用cpu进行解码,则会占用很多cpu资源。现在主流做法是利用手机gpu资源进行视频解码。
Android系统在Android4.0(API 16)增加了 MediaCodec,可以支持app调用java接口,进而使用底层硬件的音视频编解码能力。Android ndk在 Android 5.0(API21) 提供了对应的Native方法。功能大体相同。
MediaCodec 可以处理编码,也可以处理解码;可以处理音频,也可以处理视频,里面有软解(cpu),也有硬解(gpu)。
具体手机Android 系统一般会写在 media_codecs.xml 上。不同手机位置不一样。根据我的经验,大多数手机上是/system/etc/目录下。
这里主要是讲视频解码。
Android MediaCodec内部大致结构
如上图所示,mediacodec 内部有两种缓冲,一种是InputBuffer,另一种是OutputBuffer。两种缓冲的大小一般是底层硬件的代码决定。
解码过程中,Client需要不断的查询InputBuffer和OutputBuffer的情况,如果InputBuffer有空闲,则应放入相应码流;如果OutputBuffer有输出,则应该及时去消费视频帧并且释放。
codec则内部自启线程,也是不断的查询InputBuffer和OutputBuffer的情况,如果OutputBuffer有空闲并且有未处理的InputBuffer,则去解码一帧;否则挂起。
硬解码业务路线
1.代替软解的硬解码
最简单的方式,就是configure时候Surface填null,然后将解码后的数据拷贝出来。这样做的有点很明显,就是跟之前的软解逻辑基本一样,外面并不需要改变太多,之前的VideoProcess 也能接着用,也不需要渲染引擎的配合,封装性好。缺点是多了一次解码器内存到自己内存的拷贝。
2.利用解码器缓存
如果我们针对业务一的拷贝做优化,减少拷贝,这就是第二种业务路线。我们可以利用解码器的缓存进行输出存储。也就是说,我们调用ouputBuffer之后,获得输出缓存index,并不着急拷贝出图像。而是等到渲染时候,调用GetOutputBuffer获取图像指针,然后调用Image2D,进行生成gpu纹理。
3.利用GPU Image直接渲染
如果我们configure传surface,我们可以通过gpu传递的方法,直接进行渲染,这样可以减少GPU <-> CPU之间的内存拷贝。首先configure时候传surface,我们调用ouputBuffer之后,获得输出缓存index,得到渲染时候,直接调用releaseOutputBuffer(handle,idx,true),则解码器的图像直接渲染到surface图像上了。
这样虽然效率高,但是弊端也很明显,第一,那就是图像后处理做不了。第二,这种方案依赖解码器缓存,这会带来一些问题。如果解码器被提前析构,则缓存内容都没有了。又或者一些播放业务逻辑对解码器缓存要求较多(比如倒放),这也做不了。
4.利用GPU Image,SurfaceTexture类渲染到OpenGL管线
针对业务路线3,Android系统也考虑到这个问题,提供我们一种方案做折中。我们可以先建立自己的OpenGL环境,然后从建立Texture,通过Texture建立SurfaceTexture,然后取出surface,进行Configure。这样,MediaCodec的Release就渲染到SurfaceTexture类了。然后我们调用Update方法,就同步到OpenGL的Texture上了。之后可以接各种后处理,然后swapbuffer进行显示等等。
这样处理得话,基本所有的业务逻辑都可以满足。但是有一个小问题就是流畅性不足。具体为:当输出一个surface,并且OpenGL还没消费这个surface时候,解码输出是被阻塞的。也就是说,outputBuffer和OpenGL cosume 这个surface必须串行执行。如果并行,则会有覆盖的问题。
因此我们可以采取一步小调整:将OpenGL得到的Texture 拷贝一份(是GPU->GPU复制,纹理复制)。这样OpenGL就不会阻塞解码输出了。但是代价会带来拷贝性能损耗。
5.多路同步,增大流畅性
Android 6.0 (API23)新增了一个接口 —— setOutputSurface。
顾名思义,这个可以动态的设置输出的Surface。这就完美解决了上面的问题。具体为,我们可以事先建立多个Texture,然后OutputBuffer时候循环输出到任意一个空闲Texture并标记为带数据,当OpenGL消费了图像之后,将Texture回归空闲。
这样相当于在OutputBuffer和OpenGL消费之间建立了一个纹理缓冲。可以完成多线程并行的需求。
缺点很明显就是需要Android 6.0才能支持,不过现在通过Android统计面板能看到大部分手机都在Android 6.0之上。
来源:https://developer.aliyun.com/article/632892
最后欢迎大家加入 音视频开发进阶 知识星球 ,这里有知识干货、编程答疑、开发教程,还有很多精彩分享。
更多内容可以在星球菜单中找到,随着时间推移,干货也会越来越多!!!