android之媒体硬解OMX的实现

来源:互联网 发布:欧文总决赛场均数据 编辑:程序博客网 时间:2024/04/29 04:38
原址:http://blog.csdn.net/vincent_blog/article/details/7578112

Android的多媒体部分采用的编解码标准是OMX,当然这个标准是用于硬件编解码的,软件编解码在这里我就不说了。
直接从stagefright的awesomeplayer开始说起吧,如果看过我前面博客的人知道stagefright使用的三个步骤:
setdatasoure
prepare
start
至于它们的作用在这里就不多说了。
在prepare里面,当MediaExtractor解析文件后会产生一个音频流和一个视频流(可能还有字幕流)对应到stagefright里面就是一个MediaSource的数据结构。
也就是awesomeplayer里面的mVideoTrack和mAudioTrack两个数据成员。
得到音视频流后就要开始构造解码器了(暂且只说解码,编码类似)。请看initVideoDecoder或initAudioDecoder。
status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {
    mVideoSource = OMXCodec::Create(
            mClient.interface(), mVideoTrack->getFormat(),//mClient.interface() is BnOMX
            false, // createEncoder
            mVideoTrack,
            NULL, flags);
  ...
  ...
  ...
        status_t err = mVideoSource->start();

        if (err != OK) {
            mVideoSource.clear();
            return err;
        }
    }
    return mVideoSource != NULL ? OK : UNKNOWN_ERROR;
}
函数一开始就创建了一个OMXCodec,下面我们看下传进来的几个参数的意思:
===========================================================================
mClient.interface()
在awesomeplayer的构造函数里面有这么一句话    CHECK_EQ(mClient.connect(), OK);可以到OMXClient里面去看实际是通过MediaPlayerService创建了一个BnOMX(bnOMX会在后面讲到)然后作为自己的成员变量保存下来,这里我们可以将OMXClient看作OMX的客户端,BnOMX则是OMX的具体实现。
再回到mClient.interface(),就知道它返回的就是前面创建的BnOMX。
mVideoTrack->getFormat()
这个很显然是格式信息,但是这个里面不仅仅是编码格式,还有宽高,是否旋转等等,通称MetaData
false
指的是创建解码器,true则是编码器
mVideoTrack
视频流(解码前的压缩数据)
NULL
不指定解码器,如果不指定就会到现有的解码器中去找,选择第一个找到的
flags
解码器的类型,你可以在这里将解码器指定为软解码
===========================================================================
进入OMXCodec::Create函数
sp<MediaSource> OMXCodec::Create(
        const sp<IOMX> &omx,//BnOMX
        const sp<MetaData> &meta, bool createEncoder,
        const sp<MediaSource> &source,
        const char *matchComponentName,//NULL
        uint32_t flags) {
    const char *mime;
    bool success = meta->findCString(kKeyMIMEType, &mime);
    CHECK(success);
 
    Vector<String8> matchingCodecs;
    findMatchingCodecs(
            mime, createEncoder, matchComponentName, flags, &matchingCodecs);

    if (matchingCodecs.isEmpty()) {
        return NULL;
    }

    sp<OMXCodecObserver> observer = new OMXCodecObserver;
    IOMX::node_id node = 0;

    const char *componentName;
    for (size_t i = 0; i < matchingCodecs.size(); ++i) {
        componentName = matchingCodecs[i].string();
  LOGV("componentName is %s",componentName);
        sp<MediaSource> softwareCodec = createEncoder?
            InstantiateSoftwareEncoder(componentName, source, meta):
            InstantiateSoftwareCodec(componentName, source);

        if (softwareCodec != NULL) {
            LOGV("Successfully allocated software codec '%s'", componentName);

            return softwareCodec;
        }

        LOGV("Attempting to allocate OMX node '%s'", componentName);

        uint32_t quirks = getComponentQuirks(componentName, createEncoder);

        if (!createEncoder
                && (quirks & kOutputBuffersAreUnreadable)
                && (flags & kClientNeedsFramebuffer)) {
            if (strncmp(componentName, "OMX.SEC.", 8)) {
                // For OMX.SEC.* decoders we can enable a special mode that
                // gives the client access to the framebuffer contents.

                LOGW("Component '%s' does not give the client access to "
                     "the framebuffer contents. Skipping.",
                     componentName);

                continue;
            }
        }

        status_t err = omx->allocateNode(componentName, observer, &node);
        if (err == OK) {
            LOGV("Successfully allocated OMX node '%s'", componentName);

            sp<OMXCodec> codec = new OMXCodec(
                    omx, node, quirks,
                    createEncoder, mime, componentName,
                    source);

            observer->setCodec(codec);

            err = codec->configureCodec(meta, flags);

            if (err == OK) {
                return codec;
            }

            LOGV("Failed to configure codec '%s'", componentName);
        }
    }

    return NULL;
}
这里面有几个关键的函数
findMatchingCodecs
InstantiateSoftwareCodec
omx->allocateNode
下面一一进行说明:
=========================================================================
findMatchingCodecs
这个函数里面实际是会到一个CodecInfo的数组里面去找到符合条件的解码器,这个数组在OMXCodec里面定义的。当然放在前面就被放到数组的前面保存在matchingCodecs里面,
然后通过for循环来遍历这个matchingCodecs数组。

InstantiateSoftwareCodec
在看这个函数前我声明一下,我分析的代码是2.3.x的在4.0的代码里面这里会有些区别,这个看我的另一篇blog  stagefright之2.3和4.0的区别 就知道了。
在这个函数里面,会将matchingCodecs里面的解码器与一些软解码器进行比较(如果是硬解的话名字当然会不一样)。然而我们一般都是将硬解放在前面的,所以这个函数肯定会返回NULL
所以一般都会走到第三函数

omx->allocateNode
这个函数会调到BnOMX里面来。我们先看下它的三个参数componentName不多说,observer大家得留意了,它可是底层给我们上报消息的东西了,在后面会谈到,node显然是一个输出参数暂时我只能说它就是
一个区分不同解码器的标识。
下面看allocateNode的代码,这个比较重要
status_t OMX::allocateNode(
        const char *name, const sp<IOMXObserver> &observer, node_id *node) {
    Mutex::Autolock autoLock(mLock);

    *node = 0;

    OMXNodeInstance *instance = new OMXNodeInstance(this, observer);

    OMX_COMPONENTTYPE *handle;
    OMX_ERRORTYPE err = mMaster->makeComponentInstance(
            name, &OMXNodeInstance::kCallbacks,
            instance, &handle);

    if (err != OMX_ErrorNone) {
        LOGV("FAILED to allocate omx component '%s'", name);

        instance->onGetHandleFailed();

        return UNKNOWN_ERROR;
    }

    *node = makeNodeID(instance);
    mDispatchers.add(*node, new CallbackDispatcher(instance));//CallbackDispatcher dispatch the callback message from omx hardware

    instance->setHandle(*node, handle);

    mLiveNodes.add(observer->asBinder(), instance);
    observer->asBinder()->linkToDeath(this);

    return OK;
}
这里面先是创建了一个OMXNodeInstance,然后就是mMaster->makeComponentInstance再就是mDispatchers.add(*node, new CallbackDispatcher(instance))
最后instance->setHandle(*node, handle);
先简单介绍几个数据结构
OMXNodeInstance某一种类型的OMX,跟nodeid 一一对应
mMaste是OMXMaster这个说白了就是在本地OMX和硬件厂商的OMX之间做管理和协调工作的
mDispatchers是CallbackDispatcher类型的数组,它负责消息的分发(从硬件厂商获取消息分发到具体的某一种类型的解码器OMXNodeInstance,最后到前面讲的observer)
instance->setHandle(*node, handle)这里的这个handle就很关键了,所有的操作都必须通过它来完成
=========================================================================
细说一下OMXMaster
构造函数里面
OMXMaster::OMXMaster()
    : mVendorLibHandle(NULL) {
    addVendorPlugin();

#ifndef NO_OPENCORE
    addPlugin(new OMXPVCodecsPlugin);
#endif
}
在没有Opencore的情况下我们只看addVendorPlugin(),这从函数名就可以看出来就是将厂家的插件加入进来。
void OMXMaster::addVendorPlugin() {
    mVendorLibHandle = dlopen("libstagefrighthw.so", RTLD_NOW);

    if (mVendorLibHandle == NULL) {
        return;
    }

    typedef OMXPluginBase *(*CreateOMXPluginFunc)();
    CreateOMXPluginFunc createOMXPlugin =
        (CreateOMXPluginFunc)dlsym(
                mVendorLibHandle, "_ZN7android15createOMXPluginEv");

    if (createOMXPlugin) {
        addPlugin((*createOMXPlugin)());
    }
}
看到了吧,这里开始使用动态库来调用了,也就是说厂家自己替换这个库就行了。
至于要实现什么东西是有标准的,这里我就不多说了。
像CreateOMXPluginFunc这个函数是一定得有的,也就是所创建了一个厂家提供的OMX插件。
再看
void OMXMaster::addPlugin(OMXPluginBase *plugin) {
    Mutex::Autolock autoLock(mLock);

    mPlugins.push_back(plugin);

    OMX_U32 index = 0;

    char name[128];
    OMX_ERRORTYPE err;
    while ((err = plugin->enumerateComponents(
                    name, sizeof(name), index++)) == OMX_ErrorNone) {
        String8 name8(name);

        if (mPluginByComponentName.indexOfKey(name8) >= 0) {
            LOGE("A component of name '%s' already exists, ignoring this one.",
                 name8.string());

            continue;
        }

        mPluginByComponentName.add(name8, plugin);
    }
    CHECK_EQ(err, OMX_ErrorNoMore);
}
这里有一个数组保存这些插件,plugin->enumerateComponents这句话的意思是说把厂家提供的这个OMX插件支持的解码器格式一一放入到mPluginByComponentName里面,以后需要的这种格式的解码
器就到这里来找。
回到OMXMaster::makeComponentInstance这个函数,它在OMX::allocateNode中被调用的,
OMX_ERRORTYPE OMXMaster::makeComponentInstance(
        const char *name,//name is codec type 
        const OMX_CALLBACKTYPE *callbacks,
        OMX_PTR appData,
        OMX_COMPONENTTYPE **component) {
    Mutex::Autolock autoLock(mLock);

    *component = NULL;

    ssize_t index = mPluginByComponentName.indexOfKey(String8(name));

    if (index < 0) {
        return OMX_ErrorInvalidComponentName;
    }

    OMXPluginBase *plugin = mPluginByComponentName.valueAt(index);
    OMX_ERRORTYPE err =
        plugin->makeComponentInstance(name, callbacks, appData, component);

    if (err != OMX_ErrorNone) {
        return err;
    }

    mPluginByInstance.add(*component, plugin);

    return err;
}
可以看到这里就通过插件创建了实例,这里的component就是handle,也是底下返回的。callbacks就是OMXNodeInstance里面的几个回调函数OnEvent,OnEmptyBufferDone,OnFillBufferDone

总结一下操作的流程:

OMXCodec  --->   BnOMX   ---->   OMXNodeInstance  ----->  handle(看作是厂家OMX的句柄)

消息回调的流程就是

handle  ---->OnEvent(OMXNodeInstance) ------->OnEvent(BnOMX)-------->post(CallbackDispatcher)-------->onMessage(OMXNodeInstance)
----->onMessage(OMXCodecObserver)------->on_message(OMXCodec)

操作的流程大家都清楚了,下面正式进入我们最关心的,OMX是怎么来实现解码,又是怎么把解码后的数据交给我们的:
上面的操作流程里面我们知道了解码器的创建,开始解码我们必须调用OMXCodec的start函数
status_t OMXCodec::start(MetaData *meta) {
    ...
 ...
 ...
    status_t err = mSource->start(params.get());

    if (err != OK) {
        return err;
    }
 ...
 ...
 ...
 
    return init();
}
我选择了两个比较重要的地方贴了出来。
mSource->start(params.get());
这句话实际是开启了音视频流Track,也就是说准备好压缩数据给解码器去取。
init()里面会调用一个非常重要的函数allocateBuffers();从字面上看是分配内存,没错它就是分配内存的。
status_t OMXCodec::allocateBuffers() {
    status_t err = allocateBuffersOnPort(kPortIndexInput);

    if (err != OK) {
        return err;
    }

    return allocateBuffersOnPort(kPortIndexOutput);
}
它分配了两块内存,一块用于输入,一块用于输出。
至于这里面的实现不同的厂家又有所不同了,我之前做过的一个项目是从/dev/pmem_adsp这个设备中映射出来的一块内存。当然,内存的大小跟厂家提供的解码器的能力是相关的可以通过
status_t err = mOMX->getParameter(
            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
来获取(前面我们讲到了调用的流程,这里就不再多说,总之看到调OMX的最终都会到厂家自定义里面去)。申请的内存在代码中被分为一块一块的(了解camera底层实现的应该知道,这个跟camera的风格很类似)
这些内存块会放到一个叫mPortBuffers[inPort/outPort]的容器中

在awesomeplayer里面开始进行播放后不管是视频还是音频都会调OMXCodec里面的read函数。
status_t OMXCodec::read(
        MediaBuffer **buffer, const ReadOptions *options) {
    *buffer = NULL;

    ...
 ... 
 ...

    if (mInitialBufferSubmit) {
        mInitialBufferSubmit = false;

 ...
 ...
 ...
        drainInputBuffers();//key word

        if (mState == EXECUTING) {
            // Otherwise mState == RECONFIGURING and this code will trigger
            // after the output port is reenabled.
            fillOutputBuffers();//key word
        }
    }
 ...
 ...
 ...

    while (mState != ERROR && !mNoMoreOutputData && mFilledBuffers.empty()) {
  LOGV("NO MORE OUTPUT DATA=============");
        mBufferFilled.wait(mLock);//key word
    }

    if (mState == ERROR) {
        return UNKNOWN_ERROR;
    }

    if (mFilledBuffers.empty()) {
        return mSignalledEOS ? mFinalStatus : ERROR_END_OF_STREAM;
    }

    if (mOutputPortSettingsHaveChanged) {
        mOutputPortSettingsHaveChanged = false;

        return INFO_FORMAT_CHANGED;
    }

    size_t index = *mFilledBuffers.begin();
    mFilledBuffers.erase(mFilledBuffers.begin());

    BufferInfo *info = &mPortBuffers[kPortIndexOutput].editItemAt(index);//key word ,we got original data
    info->mMediaBuffer->add_ref();
    *buffer = info->mMediaBuffer;

    return OK;
}
read要做的事就是从Track里面读取数据给解码器解码后返回给awesomeplayer。而其中最关键的就是
mOMX->emptyBuffer(
            mNode, info->mBuffer, 0, offset,
            flags, timestampUs);//在drainInputBuffers中被调用
和mOMX->fillBuffer(mNode, info->mBuffer);//在fillOutputBuffers中被调用
前面一个函数是将从Track读取来的数据交给OMX解码器解码,而后一个函数就是向OMX解码器请求获取解码后的数据。
这两个操作完成后都是有回调的,至于回调的地方大家自己找吧!上面流程里面已经提到过了。

由于时间的原因后面说的比较简洁,但是大家认真看代码再结合我说的看起来应该没问题的。
先就说这么多吧!希望如果有搞驱动或硬件的能贴出来一些厂家OMX具体实现的代码,并且做下讲解,大家一起学习,感激不尽!

0 0
原创粉丝点击