美文网首页
Android多媒体框架--18:OpenMAX介绍

Android多媒体框架--18:OpenMAX介绍

作者: DarcyZhou | 来源:发表于2023-05-02 07:46 被阅读0次

    "本文转载自:[yanbixing123]的Android MultiMedia框架完全解析 - OpenMAX介绍"

    1.概述

      OpenMAX IL API通过C语言致力于打造可移植媒体组件的阵列平台。这些组件可以是来源(source)、汇出(sink)、编解码器(codec)、过滤器(filter)、分离器(splitter)、混频器(mixers),或任何其他操作。

      OpenMAX IL API允许用户加载,控制,连接和卸载各个组件。Android主要的多媒体引擎StageFright是通过IBinder使用OpenMax,用于编解码(Codec)处理。按照OpenMAX的抽象,Android本身是不关心被构造的Codec到底是硬件解码还是软件解码的。

      OpenMAX的官网如下:OpenMAX Overview - The Khronos Group Inc

    2.OMX介绍

      OpenMAX IL主要内容如下:

    • 客户端(Client):OpenMax IL的调用者;

    • 组件(Component):OpenMax IL的单元,每一个组件实现一种功能;

    • 端口(Port):组件的输入输出接口;

    • 隧道化(Tunneled):让两个组件直接连接的方式。

    01.png

      OpenMAX IL的客户端,通过调用四个OpenMAX IL组件,实现了一个功能。四个组件分别是Source组件、Host组件、Accelerator组件和Sink组件。Source组件只有一个输出端口;而Host组件有一个输入端口和一个输出端口;Accelerator组件具有一个输入端口,调用了硬件的编解码器,加速主要体现在这个环节上。Accelerator组件和Sink组件通过私有通讯方式在内部进行连接,没有经过明确的组件端口。

      OpenMAX IL在使用的时候,其数据流也有不同的处理方式:既可以经由客户端,也可以不经由客户端。图中Source组件到Host组件的数据流就是经过客户端的;而Host组件到Accelerator组件的数据流就没有经过客户端,使用了隧道化的方式;Accelerator组件和Sink组件甚至可以使用私有的通讯方式。

      OpenMAX Core是辅助各个组件运行的部分,它通常需要完成各个组件的初始化等工作,在真正运行过程中,重点是各个OpenMAX IL的组件,OpenMAX Core不是重点,也不是标准。

      OpenMAX IL的组件是OpenMax IL实现的核心内容,一个组件以输入、输出端口为接口,端口可以被连接到另一个组件上。外部对组件可以发送命令,还进行设置/获取参数、配置等内容。组件的端口可以包含缓冲区(Buffer)的队列。

      组件的处理的核心内容是:通过输入端口消耗Buffer,通过输出端口填充Buffer,由此多组件相联接可以构成流式的处理。OpenMAX IL中一个组件的结构如下图所示:

    02.png

    OpenMAX的这些组件,可以完全构成一个播放器,但是在Android中使用最多的,还是只用OMX的Accelerator组件来做编解码,因为如果纯靠CPU来做软件的编解码的话,会消耗大量的资源,甚至对于现在的高清4K视频,有的CPU甚至都解不动,所以,一般会将这个任务交给VPU(Video Process Unit)来处理,即所谓的硬解。

    3.Android中的OMX

      Android中的NuPlayer就是用openmax来做(Codec)编解码,其实在OpenMAX接口设计中,它不光能用来当编解码,它的组件也可以组成一个完整的播放器,包括source、demux、decode、output。但是Android只用它来做code,主要有如下原因:

    (1)在整个播放器中,解码器不得不说是最重要的一部分,而且也是最耗资源的一块。如果全靠软解,直接通过cpu来运算,特别是高清视频。别的事你就可以啥都不干了,所以解码器是最需要硬件提供加速的部分。现在的高清解码芯片都是主芯片+DSP结构,解码的工作都是通过DSP来做,不会在过多的占用主芯片。所有将芯片中DSP硬件编解码的能力通过openmax标准接口呈现出来,提供上层播放器来用,这块是openmax最重要的意义。

    (2)source主要是和协议打交道,demux分解容器部分,大多数的容器格式的分解是不需要通过硬件来支持。只是ts流这种格式最可能用到硬件的支持。因为ts格式比较特殊,单包的大小太小了,只有188字节。所以也是为什么现在常见的解码芯片都会提供硬件ts demux 的支持。

    (3)音视频输出部分video/audio output这块和操作系统关系十分紧密。可以看看著名开源播放器vlc。vlc 在mac、linux、Windows都有,功能上差别也不大。所以说他是跨平台的,他跨平台跨在哪?主要的工作量还是在音视频解码完之后的输出模块。因为各个系统的图像渲染和音频输出实现方法不同,所以vlc需要针对每个平台实现不同的output。这部分内容放在openmax来显然不合适。

      Android中使用的主要是OpenMax的编解码功能。虽然OpenMax也可以生成输入、输出、文件解析-构建等组件,但是在各个系统(不仅是Android)中使用的最多的还是编解码组件。媒体的输入、输出环节和系统的关系很大,引入OpenMax标准比较麻烦;文件解析-构建环节一般不需要使用硬件加速。编解码组件也是最能体现硬件加速的环节,因此最常使用。

    (不知道这里还对不对,有没有使用到OMXCodec,还是说直接使用ACodec了?下面这个图中的OMXCodec也值得商榷)

    (1)android系统中只用openmax来做codec,所以android向上抽象了一层OMXCodec,提供给上层播放器用,也就是上面所讲述的Accelerator组件。播放器中音视频解码器mVideosource、mAudiosource都是OMXCodec的实例。

    (2)OMXCodec通过IOMX 依赖binder机制 获得 OMX服务,OMX服务才是openmax在android中实现。

    (3)OMX把软编解码和硬件编解码统一看作插件的形式管理起来。

    03.png

    (4)OMX具体实现

    • OMXNodeInstance负责创建并维护不同的实例,这些实例以node_id作为唯一标识。这样播放器中每个ACodec在OMX服务端都对应有了自己的OMXNodeInstance实例。

    • OMXMaster用来维护底层软硬件解码库,根据OMXNodeInstance中想要的解码器来创建解码实体组件。

    • OMXPluginBase模板负责加载组件库,创建组件实例,由OMXMaster管理。Android原生提供的组件都是由SoftOMXPlugin类来管理,这个SoftOMXPlugin类就是继承自OMXPluginBase。(Android源码提供了一些软件解码和编码的组件,它们被抽象为SoftOMXComponent)

    • OMXClient是客户端用来与OMX IL进行通信的。

    • 内部结构CallbackDispatcher作用是用来接收回调函数的消息。

    • OMXNodeInstance同CallbackDispatcher一起完成不同实例的消息处理任务。

    (5)ACodec同OMXNodeInstance的消息传递

    • ACodec将CodecObserver observer对象通过omx->allocateNode()传递到OMXNodeInstance。

    • OMXNodeInstance将kCallbacks(OnEvent,OnEmptyBufferDone,OnFillBufferDone)传递给OMX Component

    • 当OMX Component有消息notify上来时,OMXNodeInstance最先收到,然后调用OMX.cpp。将消息在OMX.cpp里面将OMX Component thread转换到CallbackDispatcher线程中处理。CallbackDispatcher又将消息反调到OMXNodeInstance。最后调用CodecObserver 的onMessage()回到ACodec中。

    (6)ACodec与OMX组件的关系

    • ACodec ,CodecObserver和OMXNodeInstance是一一对应的,简单的可以理解它们3个构成了OpenMAX IL的一个Component,每一个node就是一个codec在OMX服务端的标识。当然还有CallbackDispatcher,用于处理codec过来的消息,通过它的post/loop/dispatch来发起接收,从OMX.cpp发送消息,最终通过OMXNodeInstance::onMessage -> CodecObserver::onMessage -> ACodec::onMessage一路往上,当然消息的来源是因为我们有向codec注册OMXNodeInstance::kCallbacks。

    • 而在OMXPluginBase创建组件实例的时候,需要传递一个callback给组件,这个callback用于接收组件的消息,它的实现是在OMXNodeInstance.cpp中。而kcallbacks是OMXNodeInstance的静态成员变量,它内部的三个函数指针分别指向了OMXNodeInstance的三个静态方法,也即是这三个方法与组件进行着消息传递。

    (7)连接OMX服务器

      在前面文档《Android多媒体框架--15:MediaCodec解析》中,MediaCodec创建成功后会执行init()函数,主要是改变内部状态为INITIALIZING,同时调用了mCodec(此时为ACodec)的initiateAllocateComponent()方法:

    mCodec->initiateAllocateComponent(format);
    ----------------------------
    void ACodec::initiateAllocateComponent(const sp &msg) {
        msg->setWhat(kWhatAllocateComponent);
        msg->setTarget(this);
        msg->post();
    }
    

    按照消息队列机制找到回调:

    ACodec::UninitializedState::onMessageReceived(const sp &msg)
     case ACodec::kWhatAllocateComponent:
            {
                onAllocateComponent(msg);
                handled = true;
                break;
            }
    

    MediaCodec::UninitializedState::onAllocateComponent()

    bool ACodec::UninitializedState::onAllocateComponent(const sp &msg) {
        ALOGV("onAllocateComponent");
    
        CHECK(mCodec->mNode == 0);
    
        OMXClient client;
        // 客户端链接OMX服务
        if (client.connect() != OK) {
            mCodec->signalError(OMX_ErrorUndefined, NO_INIT);
            return false;
        }
    
        sp omx = client.interface();
    
        sp notify = new AMessage(kWhatOMXDied, mCodec);
    
        Vector matchingCodecs;
    
        AString mime;
    
        AString componentName;
        uint32_t quirks = 0;
        int32_t encoder = false;
        if (msg->findString("componentName", &componentName)) {
            sp list = MediaCodecList::getInstance();
            if (list != NULL && list->findCodecByName(componentName.c_str()) >= 0) {
                matchingCodecs.add(componentName);
            }
        } else {
            CHECK(msg->findString("mime", &mime));
    
            if (!msg->findInt32("encoder", &encoder)) {
                encoder = false;
            }
    
            // 找出符合要求的解码器
            MediaCodecList::findMatchingCodecs(
                    mime.c_str(),
                    encoder, // createEncoder
                    0,       // flags
                    &matchingCodecs);
        }
    
        sp observer = new CodecObserver;
        IOMX::node_id node = 0;
    
        status_t err = NAME_NOT_FOUND;
        for (size_t matchIndex = 0; matchIndex < matchingCodecs.size();
                ++matchIndex) {
            componentName = matchingCodecs[matchIndex];
            quirks = MediaCodecList::getQuirksFor(componentName.c_str());
    
            pid_t tid = gettid();
            int prevPriority = androidGetThreadPriority(tid);
            androidSetThreadPriority(tid, ANDROID_PRIORITY_FOREGROUND);
            // 创建真正的解码器实例
            err = omx->allocateNode(componentName.c_str(), observer, &mCodec->mNodeBinder, &node);
            androidSetThreadPriority(tid, prevPriority);
    
            if (err == OK) {
                break;
            } else {
                ALOGW("Allocating component '%s' failed, try next one.", componentName.c_str());
            }
    
            node = 0;
        }
    
        if (node == 0) {
            if (!mime.empty()) {
                ALOGE("Unable to instantiate a %scoder for type '%s' with err %#x.",
                        encoder ? "en" : "de", mime.c_str(), err);
            } else {
                ALOGE("Unable to instantiate codec '%s' with err %#x.", componentName.c_str(), err);
            }
    
            mCodec->signalError((OMX_ERRORTYPE)err, makeNoSideEffectStatus(err));
            return false;
        }
    
        mDeathNotifier = new DeathNotifier(notify);
        if (mCodec->mNodeBinder == NULL ||
                mCodec->mNodeBinder->linkToDeath(mDeathNotifier) != OK) {
            // This was a local binder, if it dies so do we, we won't care
            // about any notifications in the afterlife.
            mDeathNotifier.clear();
        }
    
        notify = new AMessage(kWhatOMXMessageList, mCodec);
        observer->setNotificationMessage(notify);
    
        mCodec->mComponentName = componentName;
        mCodec->mRenderTracker.setComponentName(componentName);
        mCodec->mFlags = 0;
    
        if (componentName.endsWith(".secure")) {
            mCodec->mFlags |= kFlagIsSecure;
            mCodec->mFlags |= kFlagIsGrallocUsageProtected;
            mCodec->mFlags |= kFlagPushBlankBuffersToNativeWindowOnShutdown;
        }
    
        mCodec->mQuirks = quirks;
        mCodec->mOMX = omx;
        mCodec->mNode = node;
    
        {
            sp notify = mCodec->mNotify->dup();
            notify->setInt32("what", CodecBase::kWhatComponentAllocated);
            notify->setString("componentName", mCodec->mComponentName.c_str());
            notify->post();
        }
    
        // 这里设置的是ACodec的状态
        mCodec->changeState(mCodec->mLoadedState);
    
        return true;
    }
    

    这里首先是client.connect() 去连接服务器,看下这块的实现:

    • OMXClient.cpp
    status_t OMXClient::connect() {
        sp<IServiceManager> sm = defaultServiceManager();
        sp<IBinder> playerbinder = sm->getService(String16("media.player"));
        sp<IMediaPlayerService> mediaservice = interface_cast<IMediaPlayerService>(playerbinder);
    
        if (mediaservice.get() == NULL) {
            ALOGE("Cannot obtain IMediaPlayerService");
            return NO_INIT;
        }
    
        sp<IOMX> mediaServerOMX = mediaservice->getOMX();
        if (mediaServerOMX.get() == NULL) {
            ALOGE("Cannot obtain mediaserver IOMX");
            return NO_INIT;
        }
    
        // If we don't want to use the codec process, and the media server OMX
        // is local, use it directly instead of going through MuxOMX
        if (!sCodecProcessEnabled &&
                mediaServerOMX->livesLocally(0 /* node */, getpid())) {
            mOMX = mediaServerOMX;
            return OK;
        }
    
        sp<IBinder> codecbinder = sm->getService(String16("media.codec"));
        sp<IMediaCodecService> codecservice = interface_cast<IMediaCodecService>(codecbinder);
    
        if (codecservice.get() == NULL) {
            ALOGE("Cannot obtain IMediaCodecService");
            return NO_INIT;
        }
    
        sp<IOMX> mediaCodecOMX = codecservice->getOMX();
        if (mediaCodecOMX.get() == NULL) {
            ALOGE("Cannot obtain mediacodec IOMX");
            return NO_INIT;
        }
    
        mOMX = new MuxOMX(mediaServerOMX, mediaCodecOMX);
    
        return OK;
    }
    

    它通过获取mediaplayer和mediacodec这两个service来分别获取OMX服务,而在MediaPlayerService.cpp中,它new出来一个OMX:

    sp<IOMX> MediaPlayerService::getOMX() {
        Mutex::Autolock autoLock(mLock);
    
        if (mOMX.get() == NULL) {
            mOMX = new OMX;
        }
    
        return mOMX;
    }
    

    同样在MediaCodecService.cpp中,它也是new出来一个OMX:

    sp<IOMX> MediaCodecService::getOMX() {
    
        Mutex::Autolock autoLock(mLock);
    
        if (mOMX.get() == NULL) {
            mOMX = new OMX;
        }
    
        return mOMX;
    }
    

    在OMX的构造函数中,创建了一个OMXMaster:

    Omx::Omx() :
        mMaster(new OMXMaster()),
        mParser() {
    }
    

    那么OpenMAX就从OMXMaster开始讲起。

    相关文章

      网友评论

          本文标题:Android多媒体框架--18:OpenMAX介绍

          本文链接:https://www.haomeiwen.com/subject/fmsqjdtx.html