zoukankan      html  css  js  c++  java
  • vlc源码分析(六) 调用OpenMAX硬解码H.265

      H.265(HEVC)编码格式能够在得到相同编码质量视频的前提下,使用相当于H.264(AVC)一半的存储容量,虽然H.265的算法复杂度比H.264高一个数量级,但是硬件水平在不断提高,因此H.265使用场合逐渐多了起来。好多硬件厂商芯片内部实现了H.265的硬解码。最近调试了vlc-android调用OpenMAX硬解码H.265的部分,使用的硬件平台是ZX-2000,系统是Android5.1。

      OpenMAX的官方网站:https://www.khronos.org/openmax/

      官方文档整理:https://github.com/jiayayao/DataSheet/tree/master/encode-decode/openmax

      官方介绍:The OpenMAX Working Group has been formed by the Khronos Group, to define a set of standard, open Application Programming Interfaces (APIs) for multimedia applications. The goal of this open standard is to reduce the cost and complexity of porting multimedia software to new processors and architectures.  

      可见OpenMAX的核心价值在于抽象了各个硬件平台和框架,减少了多媒体应用软件移植的复杂度。OpenMAX实际上分成三个层次,自上而下分别是,OpenMAX AL(应用层),OpenMAX IL(集成层)和OpenMAX DL(开发层)。在实际的应用中,OpenMAX的三个层次中使用较多的是集成层OpenMAX IL。三个层次的内容如下所示。

      第一层:OpenMAX AL(Appliction Layer,应用层),在应用程序和多媒体中间件之间提供了一个标准化接口,多媒体中间件提供服务以实现被期待的API功能;

      第二层:OpenMAX IL(Integration Layer,集成层),作为音频、视频和图像编解码器能与多媒体编解码器交互,并以统一的行为支持组件(例如,资源和皮肤)。这些编解码器或许是软硬件的混合体,对用户是透明的底层接口应用于嵌入式、移动设备。它提供了应用程序和媒体框架,透明的。编解码器供应商必须写私有的或者封闭的接口,集成进移动设备。IL的主要目的是使用特征集合为编解码器提供一个系统抽象,为解决多个不同媒体系统之间轻便性的问题;

      第三层:OpenMAX DL(Development Layer,开发层),定义了一个API,它是音频、视频和图像功能的集合。供应商能够在一个新的处理器上实现并优化,然后编解码供应商使用它来编写更广泛的编解码器功能。它包括音频信号的处理功能,如FFT和filter,图像原始处理,如颜色空间转换、视频原始处理,以实现例如MPEG-4、H.264、MP3、AAC和JPEG等编解码器的优化。 

      OpenMAX对硬解码的事务进行了抽象,提供了一系列的.h文件,各个厂家分别根据头文件实现OpenMAX的标准。在vlc-android工程中,通过调用mediacodec.c文件,查询Android系统的MediaCodec是否支持硬解码,如果支持,则创建对应的硬解码器。mediacodec.c文件的作用是封装Android系统的Mediacodec框架,同时OpenMAX及其实现会作为对应的解码器注册到MediaCodec框架中。当mediacodec_ndk.c文件中调用Start函数创建解码器时:

    p_sys->p_codec = syms.AMediaCodec.createCodecByName(api->psz_name);

    该语句实际上调用的是MediaCodec框架的AMediaCodec_createCodecByName函数(Android5.1源码:NdkMediaCodec.cpp). Android系统的MediaCodec与OpenMAX的关系如下图: 

     

    对H265编码格式的视频来说,创建的解码器名称是:OMX.ZX.video.decoder.hevc,也就是说实际的硬解码走的是ZX-2000对OpenMAX的实现的H265部分,硬解码时打印的日志大部分都是以“S3OMX”等开头也证明了这一点”。从OpenMAX的AL, IL, DL三个层次的理解上来看,mediacodec.c文件及该文件之上的文件封装了OpenMAX,使得OpenMAX在vlc中得以利用,应该属于AL层;ZX-2000的BSP开发部门实现了OpenMAX标准(代码以“S3OMX”开头),BSP中对OpenMAX的实现应该属于IL层;DL层应该是芯片内部硬解码部分的驱动实现。所以对于应用层的开发人员来说,应该了解AL和IL层。

    一、OpenMAX IL  

      OpenMAX IL层标准提供了几个头文件:

    OMX_Types.h:OpenMAX Il的数据类型定义
    OMX_Core.h:OpenMAX IL核心的API
    OMX_Component.h:OpenMAX IL 组件相关的 API
    OMX_Audio.h:音频相关的常量和数据结构
    OMX_IVCommon.h:图像和视频公共的常量和数据结构
    OMX_Image.h:图像相关的常量和数据结构
    OMX_Video.h:视频相关的常量和数据结构
    OMX_Other.h:其他数据结构(包括A/V 同步)
    OMX_Index.h:OpenMAX IL定义的数据结构索引
    OMX_ContentPipe.h:内容的管道定义

      各个硬件厂商需要根据这些头文件实现OpenMAX标准。比如以Android5.1源码为例,TI的OMAP芯片实现OpenMAX的代码就在文件android5.1hardware iomap4xxx domxomx_coresrcOMX_Core.c中,其他厂商如果要实现硬解码,也需要按照类似的做法,将自身对于OpenMAX的实现添加到Android系统BSP中。

      Android5.1源码的android5.1hardware iomap4xxxdomx estsample_proxy est_sample_ proxy.c文件是TI的OMAP芯片OpenMAX部分的简单测试程序。该测试程序仅仅是简单的初始化Component,处理缓冲区,最后通过改变Component状态释放Component。如果需要适配OpenMAX到新的芯片,可以参考OMAP的实现。

    二、OpenMAX AL   

      mediacode.c和avcodec.c作用是类似的,mediacodec.c为OpenMAX在VLC下的使用提供了封装,avcodec.c为FFmpeg在VLC下的使用提供了封装。提供封装时,一般需要提供几个函数,如OpenDecoder,CloseDecoder,DecodeBlock等,这些函数会被上层调用。

      当获取到的RTSP流是H.265编码格式后,mediacodec_ndk.c创建解码器“OMX.ZX.video.decoder.hevc”,创建解码器时,会将mediacodec_ndk.c实现的一系列函数赋值给mc_api结构体,包括start(), stop(), configure(), clean()等。解码时解码线程DecodeThread会调用DecodeBlock函数解码数据块,但是与avcodec(软解)的直接解码不同,mediacodec的解码线程是将需要解码的数据块(block_t类型)通过API经mediacodec_ndk.c送入硬解码芯片队列中,另外开启一个新的线程OutThread来从硬解码芯片的图像队列中取出图像的index,赋值一些时间戳信息,再推入图像fifo中,交给渲染线程处理。

      注意,硬解码时推入图像fifo的picture_t只是带有硬件缓冲区的index,最后渲染线程只是根据index将该缓冲区释放(modules/video_out/android/display.c);而软解码时,FFmepg会将带有解码后图像数据的picture_t交给opengl(modules/video_output/opengl/display.c)去渲染。个人的理解是,硬解码时,解码后的图像就直接在GPU里边,GPU在恰当的时机直接把图像交给显示的framebuffer就可以了,无需再取出来再交给opengl渲染;软解码时,解码后的图像在系统内存中,需要交给opengl去渲染。

      解码线程堆栈如下:

      硬解码模块自行创建的输出线程堆栈如下:

       注意,在将图像推入图像fifo之前,会先调用API从硬解码芯片的图像队列中取出图像,调用mediacodec_ndk.c文件的DequeOutput函数。 

    参考资料:

    《Android系统级深入开发—移植与调试》中OpenMAX章节

  • 相关阅读:
    JS实现继承,封装一个extends方法
    JS实现new关键字的功能
    前端常见原生方法的实现(bind,promise,new,extends,深拷贝,函数防抖,函数节流)
    Nodejs ORM框架Sequelize快速入门
    Nodejs ORM框架Sequelize(模型,关联表,事务,循环,及常见问题)
    NodeJs mysql 开启事务
    web开发的跨域问题详解
    docker网络
    docker容器的学习
    路飞学城的部署
  • 原文地址:https://www.cnblogs.com/jiayayao/p/6964506.html
Copyright © 2011-2022 走看看