您的位置:首页 > Web前端

分配input port buffers、提取视频文件压缩数据、复制压缩数据到input buffer、H.264硬件解码器开始解码过程分析

2016-09-10 19:33 896 查看
这篇文章介绍Android multimedia框架中分配用于存储压缩的视频数据的input port buffers、提取视频文件中的压缩数据、复制提取后的视频压缩数据到input port buffer,之后exynos H.264解码器解码input port buffer中的压缩数据。

在OMXCodec::allocateBuffersOnPort中分配input port buffers

status_t OMXCodec::allocateBuffersOnPort(OMX_U32 portIndex) {
1.
    err = mOMX->getParameter(
            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));

上面mOMX->getParameter函数调用中有一个binder进程间通讯,它最终会调用:

#define OMX_GetParameter(                                   \
        hComponent,                                         \
        nParamIndex,                                        \
        pComponentParameterStructure)                        \
    ((OMX_COMPONENTTYPE*)hComponent)->GetParameter(         \
        hComponent,                                         \
        nParamIndex,                                        \
        pComponentParameterStructure)    /* Macro End */

hComponent(handle)在allocateNode函数调用中确定,对于三星exynos、h.264的情况,GetParameter指向Exynos_H264Dec_GetParameter,这个

指针同样是在allocateNode函数调用中确定。下面看一下这个函数调用:

OMX_ERRORTYPE Exynos_OMX_GetParameter(
    case OMX_IndexParamPortDefinition:
        Exynos_OSAL_Memcpy(portDefinition, &pExynosPort->portDefinition, portDefinition->nSize);

上面pExynosPort->portDefinition结构在allocateNode函数调用中执行如下函数设置:

OSCL_EXPORT_REF OMX_ERRORTYPE Exynos_OMX_ComponentInit(OMX_HANDLETYPE hComponent, OMX_STRING componentName)
    /* Input port */
    pExynosPort = &pExynosComponent->pExynosPort[INPUT_PORT_INDEX];
    pExynosPort->portDefinition.format.video.nFrameWidth = DEFAULT_FRAME_WIDTH;
    pExynosPort->portDefinition.format.video.nFrameHeight= DEFAULT_FRAME_HEIGHT;
    /* Output port */
    pExynosPort = &pExynosComponent->pExynosPort[OUTPUT_PORT_INDEX];
    pExynosPort->portDefinition.format.video.nFrameWidth = DEFAULT_FRAME_WIDTH;
    pExynosPort->portDefinition.format.video.nFrameHeight= DEFAULT_FRAME_HEIGHT;

2.

返回到OMXCodec::allocateBuffersOnPort,通过mOMX->getParameter函数调用得到了一个OMX_PARAM_PORTDEFINITIONTYPE结构后,计算totalSize:

size_t totalSize = def.nBufferCountActual * def.nBufferSize;

def.nBufferCountActual在allocateNode函数调用中被初始化为0. 这个变量表示buffer的数量,现在暂时还不知道这个变量是怎么设置的。

def.nBufferSize的值是DEFAULT_VIDEO_INPUT_BUFFER_SIZE:

#define DEFAULT_FRAME_WIDTH                 176
#define DEFAULT_FRAME_HEIGHT                144
#define DEFAULT_VIDEO_INPUT_BUFFER_SIZE    (DEFAULT_FRAME_WIDTH * DEFAULT_FRAME_HEIGHT) * 2


3.

接下来执行如下函数:

 err = mOMX->allocateBuffer(
   mNode, portIndex, def.nBufferSize, &buffer,
   &info.mData);
这个函数调用中包含一个binder进程间通讯,看一下这个函数调用的后面部分:

status_t OMXNodeInstance::allocateBuffer(
        OMX_U32 portIndex, size_t size, OMX::buffer_id *buffer,
        void **buffer_data) {
在这个函数中,通过调用如下的宏:

    OMX_ERRORTYPE err = OMX_AllocateBuffer(
            mHandle, &header, portIndex, buffer_meta, size);
其中mHandle类型是OMX_HANDLETYPE,就是一空指针,并且在这个宏函数中强制转化为了一个OMX_COMPONENTTYPE *. 这个宏函数确定

OMX_BUFFERHEADERTYPE *header,这个指针是关键。OMX_AllocateBuffer是一个宏,它调用了OMX_COMPONENTTYPE中的AllocateBuffer,它是一个

函数指针,在本例中,它指向Exynos_OMX_AllocateBuffer。在OMX_AllocateBuffer宏中,有一个pAppPrivate的参数,这个参数用于直接赋值给

这里的OMX_BUFFERHEADERTYPE *header中的pAppPrivate成员。这个函数会分配参数size大小的buffer,并将地址赋值给OMX_BUFFERHEADERTYPE header

中的pBuffer(条件是这个buffer的类型不是SECURE_MEMORY,如果是这种类型,pBuffer的值是分配的buffer的fd)。 确定这个指针之后,根据它执行:

    *buffer = makeBufferID(header);
    *buffer_data = header->pBuffer;

第一行的函数调用中,会以KeyedVector mBufferIDToBufferHeader当前的index为key(类型OMX::buffer_id),OMX_BUFFERHEADERTYPE *header为value组成一个键值对存入

KeyedVector mBufferIDToBufferHeader。对于KeyVector mBufferHeaderToBufferID和mBufferIDToBufferHeader是类似的,只是两者的key-value倒置了。

最后这个函数返回这个key-value的key(类型OMX::buffer_id)。这个返回值赋值给OMX::buffer_id buffer参数。

第二行根据header->pBuffer的值设置buffer_data二重指针指向的指针,这个指针就是BufferInfo中的mData。
在OMXNodeInstance::allocateBuffer的最后,根据OMX_BUFFERHEADERTYPE创建一个CodecBuffer对象,并把它插入Vector<CodecBuffer> mCodecBuffers,这个Vector在emptyBuffer的回调中有使用到

4.

返回到OMXCodec::allocateBuffersOnPort中。在mOMX->allocateBuffer函数调用中,确定了IOMX::buffer_id buffer以及info.mData,接下来将

BufferInfo info中的mBuffer成员设置为这个IOMX::buffer_id buffer,然后执行函数mPortBuffers[portIndex].push(info)将这个info push到

mPortBuffers[portIndex] Vector中,这个portIndex是input port index。

在OMXCodec::configureCodec中将压缩的视频视频数据提取到Vector<CodecSpecificData *>中

status_t OMXCodec::configureCodec(const sp<MetaData> &meta) {
        } else if (meta->findData(kKeyHVCC, &type, &data, &size)) {
            if ((err = parseHEVCCodecSpecificData(
                            data, size, &profile, &level)) != OK) {
                ALOGE("Malformed HEVC codec specific data.");
                return err;
            }
上面parseHEVCCodecSpecificData函数对sp<MetaData> data(于mVideoTrack关联了)中的数据执行OMXCodec::addCodecSpecificData函数。对于这部分数据

的结构,这些数据由若干个array组成,每一个array由多个NAL组成,这个函数将所有的NAL push到Vector<CodecSpecificData *> mCodecSpecificData中。

在OMXCodec::read/drainInputBuffers中复制视频压缩数据到input port buffer,作为H.264解码器解码的输入数据,H.264解码器解码这个压缩的视频数据unit

在这个函数调用中,将Vector<CodecSpecificData *> mCodecSpecificData中元素中的数据(压缩的视频数据)复制到BufferInfo中的mData指向的buffer。mData和OMX_BUFFERHEADERTYPE中的pBuffer的指向是一样的,都指向exynos H.264硬件解码器中分配的input port buffer。
status_t OMXCodec::read(
    if (mInitialBufferSubmit) {
        mInitialBufferSubmit = false;
        drainInputBuffers();
        if (mState == EXECUTING) {
            // Otherwise mState == RECONFIGURING and this code will trigger
            // after the output port is reenabled.
            fillOutputBuffers();
        }

上面mInitialBufferSubmit在创建OMXCodec对象时,在OMXCodec的构造函数中被设置为true,这就是创建mVideoSource对象时它被设置为

true。另外在OMXCodec::start函数中,它也会被设置为true。因为现在是要读取本地视频文件中的压缩数据进行解压缩,所以OMXCodec::start

函数肯定在这之前被调用了,所以这个变量这时毫无疑问是true。所以执行这个if中的代码,在这些代码中,首先将这个变量设置为false,

这个变量被设置为false的唯一一处引用就是这里;

接下来执行drainInputBuffers().这个函数比较复杂,下面分析一下这个函数:

1.1 drainInputBuffers

void OMXCodec::drainInputBuffers() {
    CHECK(mState == EXECUTING || mState == RECONFIGURING);
    if (mFlags & kUseSecureInputBuffers) {
    } else {
        Vector<BufferInfo> *buffers = &mPortBuffers[kPortIndexInput];
        for (size_t i = 0; i < buffers->size(); ++i) {
            BufferInfo *info = &buffers->editItemAt(i);
            if (info->mStatus != OWNED_BY_US) {
                continue;
            }
            if (!drainInputBuffer(info)) {
                break;
            }
上面先检查OMXCodec结构中的mState的值是不是EXECUTING或者RECONFIGURING,如果不是出错,这里先假设它是这两个值之一,满足条件;

然后检查OMXCodec中的mFlags成员有没有kUseSecureInputBuffers标志,因为if中的drainAnyInputBuffer函数调用参数感觉挺奇怪的,所以

这里先假设mFlags中没有这个标志,分析else部分的代码。在这部分代码中,mPortBuffers是一个Vector<BufferInfo>类型的数组,这里

取这个数组中的index为kPortIndexInput(它的值是0)的元素,就是说是取input的数组元素,这个Vector<BufferInfo>类型的数组元素

中包含了许多input buffer。接下来在一个循环中依次将这个Vector<BufferInfo>类型的数组元素中的input buffer(用BufferInfo对象

来表示)取出来,取BufferInfo对象的方法是执行buffers->editItemAt(i)函数,取出BufferInfo对象后,检查这个对象中的mStatus成员

是否等于OWNED_BY_US,注意在allocateBuffersOnPort函数中分配nBufferCountActual数目的buffer时,已经将info->mStatus设置为了OWNED_BY_US。

如果不等于,继续去下一个BufferInfo对象,否则执行drainInputBuffer函数,参数是当前的BufferInfo对象。

drainInputBuffer函数比较复杂,下面分析一下这个函数:

bool OMXCodec::drainInputBuffer(BufferInfo *info) {
    if (mCodecSpecificDataIndex < mCodecSpecificData.size()) {
        CHECK(!(mFlags & kUseSecureInputBuffers));
        const CodecSpecificData *specific =
            mCodecSpecificData[mCodecSpecificDataIndex];
        size_t size = specific->mSize;
        if ((!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mMIME) ||
             !strcasecmp(MEDIA_MIMETYPE_VIDEO_HEVC, mMIME))
                && !(mQuirks & kWantsNALFragments)) {
        } else {
            CHECK(info->mSize >= specific->mSize);
            memcpy(info->mData, specific->mData, specific->mSize);
        }
        status_t err = mOMX->emptyBuffer(
                mNode, info->mBuffer, 0, size,
                OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_CODECCONFIG,
                0);
        info->mStatus = OWNED_BY_COMPONENT;
        ++mCodecSpecificDataIndex;
        return true;
上面mCodecSpecificDataIndex从mCodecSpecificData数组中取出对应的CodecSpecificData指针出来,赋值给specific,上面有一个

比较mMIME的值的if,这里出于容易分析的目的,先假设这个if条件不成立,执行else,在else中先检查参数BufferInfo中的mSize的

值和specifiOc对象的mSize的值,需要前者大于后者。这里先假设这个条件成立,所以接下来执行memcpy函数,将specific中的数据

拷贝到BufferInfo info中,拷贝的大小是specific->mSize。specific数据是在创建OMXCodec时,config这个codec时,根据视频文件创建的,这些

specific数据保存在一个mPortBuffers[portIndex] Vector中,这些specific数据是未解码的视频数据。所以BufferInfo info中的数据是压缩的

视频数据;

接下来执行mOMX->emptyBuffer(),这个函数调用中有一个binder进程间通讯,函数调用前面、中间的过程这里不做分析,看它的后面部分:

status_t OMXNodeInstance::emptyBuffer(
        OMX::buffer_id buffer,
        OMX_U32 rangeOffset, OMX_U32 rangeLength,
        OMX_U32 flags, OMX_TICKS timestamp) {
    OMX_BUFFERHEADERTYPE *header = findBufferHeader(buffer);
    header->nFilledLen = rangeLength;
    header->nOffset = rangeOffset;
    header->nFlags = flags;
    header->nTimeStamp = timestamp;
    BufferMeta *buffer_meta =
        static_cast<BufferMeta *>(header->pAppPrivate);
    buffer_meta->CopyToOMX(header);
    OMX_ERRORTYPE err = OMX_EmptyThisBuffer(mHandle, header);

上面根据OMX::buffer_id buffer由函数findBufferHeader得到对应的OMX_BUFFERHEADERTYPE *,这个结构体中的pBuffer成员指向实际未解码的视频

数据。这部分数据是在OMXCodec::drainInputBuffer函数中复制过去的,这个复制操作的dest是BufferInfo的mData,这个指针是在allocateBuffersOnPort

函数中,在分配input port buffer时,由OMX_BUFFERHEADERTYPE中的pBuffer的值赋值给mData的。所以在OMXCodec::drainInputBuffer中将

压缩视频数据复制到BufferInfo中的mData指向的dest的操作,实际上是复制到OMX_BUFFERHEADERTYPE中的pBuffer所指向的dest。

OMX_EmptyThisBuffer是一个宏:

#define OMX_EmptyThisBuffer(                                \
        hComponent,                                         \
        pBuffer)                                            \
    ((OMX_COMPONENTTYPE*)hComponent)->EmptyThisBuffer(      \
        hComponent,                                         \
        pBuffer)                        /* Macro End */

mHandle在OMXCodec::create --> OMX::allocateNode函数调用中被设置,以三星exynos为例,OMX_COMPONENTTYPE结构体中的EmptyThisBuffer函数指针

指向Exynos_OMX_EmptyThisBuffer,所以执行Exynos_OMX_EmptyThisBuffer。在这个函数中,创建一个EXYNOS_OMX_MESSAGE message,这个message

的pCmdData指向参数OMX_BUFFERHEADERTYPE *pBuffer,然后将这个message插入pExynosPort->bufferQ last队列中:
    message->messageType = EXYNOS_OMX_CommandEmptyBuffer;
    message->messageParam = (OMX_U32) i;
    message->pCmdData = (OMX_PTR)pBuffer;
    ret = Exynos_OSAL_Queue(&pExynosPort->bufferQ, (void *)message);

上面在H.264 硬件解码器中将压缩的视频数据块插入了input port的bufferQ中,至于解码器从这个queue中取得压缩的视频数据块,然后进行解码的过程将在下一篇文章中介绍。
注意在OMXCodec::drainInputBuffer函数中,mCodecSpecificData.size()的返回值是当前的视频文件提取出来的小压缩数据块的数目,一次取这个Vector中的一个元素,每次mCodecSpecificDataIndex加1,当mCodecSpecificDataIndex的值等于mCodecSpecificData.size()时,表示这个视频文件已经解码完了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: