现在的位置: 首页 > 综合 > 正文

Android_ICS_OMX_In_Stagefright——>2开始解码(软解)

2013年01月07日 ⁄ 综合 ⁄ 共 7171字 ⁄ 字号 评论关闭

当应用层调用mediaplayer.start()的时候,在framework层对应的是在awesomeplayerpost一个mVideoEventTimedEventQueue中等待被调度。

        当其被调度到的时候,会激活回调函数onVideoEvent
        在这个回调函数中,会做音视频的同步处理。代码很长捡关键的贴。
        void AwesomePlayer::onVideoEvent() {

        for (;;) {
	...
            status_t err = mVideoSource->read(&mVideoBuffer, &options);
 	...
            initRenderer_l();
	...
             mVideoRenderer->render(mVideoBuffer); 
    }
	
在这个回调函数中可以看到这样一句话,status_t err = mVideoSource->read(&mVideoBuffer, &options);其中,mVideoBuffer 是一个MediaBuffer类型的成员变量。还记得mVideoSource是什么类型吗?这里的mVideoSource,就是前面返回的OMXCodec,那么实际调用是的:
        status_t OMXCodec::read(MediaBuffer **buffer, const ReadOptions *options),这个read函数会填充MediaBuffer *mVideoBuffer这个成员变量,然后交给Renderer来渲染输出。看看read函数的实现:
         
        status_t OMXCodec::read(MediaBuffer **buffer, const ReadOptions *options) {
        …...
        drainInputBuffers();
        if (mState == EXECUTING) {
            fillOutputBuffers();
        }
    }
        …...

    while (mState != ERROR && !mNoMoreOutputData && mFilledBuffers.empty()) {
        if ((err = waitForBufferFilled_l()) != OK) {
            return err;
        }
    }

    if (mState == ERROR) {
        return UNKNOWN_ERROR;
    }
    if(seeking) {
        CHECK_EQ((int)mState, (int)FLUSHING);
        setState(EXECUTING);
    }

    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);
    CHECK_EQ((int)info->mStatus, (int)OWNED_BY_US);
    info->mStatus = OWNED_BY_CLIENT;

    info->mMediaBuffer->add_ref();
    *buffer = info->mMediaBuffer;

    return OK;
}

在这个read函数中最重要的两个方法是drainInputBuffers()fillOutputBuffers(),可以看到这两个函数是先后执行的。先来看看drainInputBuffers()方法中,做了哪些操作。

        void OMXCodec::drainInputBuffers() {
        …...
        Vector<BufferInfo> *buffers = &mPortBuffers[kPortIndexInput];
        for (size_t i = 0; i < buffers->size(); ++i) {
            BufferInfo *info = &buffers->editItemAt(i);
         …...
           
            if (!drainInputBuffer(info)) {
                break;
            }
         …...
}
这个 drainInputBuffers函数从输入端口的buffers中取出之前分配好的BufferInfo,然后交给drainInputBuffer(info)函数来处理,函数实现如下:

bool OMXCodec::drainInputBuffer(BufferInfo *info) {
    
        //comment by Alan, this buffer has only codec config informations
        status_t err = mOMX->emptyBuffer(
                mNode, info->mBuffer, 0, size,
                OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_CODECCONFIG,
                0);
       
    for (;;) {
        ….... 
        MediaBuffer *srcBuffer;
        …...
        err = mSource->read(&srcBuffer);
        …...
        memcpy((uint8_t *)info->mData + offset,
                        (const uint8_t *)srcBuffer->data()
                            + srcBuffer->range_offset(),
                        srcBuffer->range_length());
        ….....	
        err = mOMX->emptyBuffer(
            mNode, info->mBuffer, 0, offset,
            flags, timestampUs);
        …....
}

这个函数比较复杂,主要的关键代码如上,在这个函数中,首先会把一些和编解码组件相关的specific data送给omx框架来调用一次emptybuffer,然后会进入一个for(;;)循环,在这个for循环内,会通过mSource->read(&srcBuffer)srcBuffer中填充一些原始的流数据(从XXXExtractor解析出来未解码的数据),这里mSource就是前面XXXExtractor在文件解析的过程中new XXXSource这里假定是MPEG4Source。函数的大致实现如下:

       status_t MPEG4Source::read(MediaBuffer **out, const ReadOptions *options) {
        …...
        err = mGroup->acquire_buffer(&mBuffer);

        
        if (usesDRM) {
            num_bytes_read =
                mDataSource->readAt(offset, (uint8_t*)mBuffer->data(), size);
        } else {
            num_bytes_read = mDataSource->readAt(offset, mSrcBuffer, size);
        }

        …...
        *out = mBuffer;
        mBuffer = NULL;
        return OK;
}
这个函数的实现很复杂,在read的时候不但考虑了seek的问题,而且还涉及到了不少多媒体容器格式方面的一些问题。但是关于数据流向的函数就是两个,首先acquire_buffer,这个acquire_buffer和前面的add_buffer对应,在MPEG4Source::start中会被调用到。获得一个mBuffer后就可以通过mDataSource->readAt来往这个buffer上填充数据了。这里的mDataSource实际上可以对应到一个FileSource或者来之网络的CacheSourcemBuffer数据填充完后赋值给调用函数的 srcBuffer,这样就相当于给 drainInputBuffer(BufferInfo *info)中的info填充了数据,然后回到drainInputBuffer函数,接着emptyBuffer。实际上这里的BufferInfo *info的数据填充过程还有一些细节需要弄清楚,BufferInfo的结构体各个字段的意义还有待进一步弄明白。

        到了mOMX->emptyBuffer()基本就进入OMX框架了,无非是OMX那一套消息机制,之前已经说过,这里就不在赘述了。如果还有不清楚的话,其在openMax文档的时候我画的一张时序图。还有一点需要明确,在emptyBuffer的时候会将App Data  copyToOMX,将来之Appcation的数据copyOMX框架中,而在OMXNodeInstance::onMessage  FILL_BUFFER_DONE的时候,会将数据从OMX框架中copyAppcation空间,即copyFromOMXAppcation<--  data --->OMX
       来看看其中一个函数的实现,
       void CopyToOMX(const OMX_BUFFERHEADERTYPE *header) {
        if (!mIsBackup) {
            return;
        }

        memcpy(header->pBuffer + header->nOffset,
               (const OMX_U8 *)mMem->pointer() + header->nOffset,
               header->nFilledLen);
    }
注意以下,mMem这个成员变量具体指什么,我们看看emptyBuffer这个函数的实现:

       status_t OMXNodeInstance::emptyBuffer(
        OMX::buffer_id buffer,
        OMX_U32 rangeOffset, OMX_U32 rangeLength,
        OMX_U32 flags, OMX_TICKS timestamp) {
    Mutex::Autolock autoLock(mLock);

    OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)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);

    return StatusFromOMXError(err);
}

去看一下BufferMeta的构造函数就可以发现,上面mMem实际上指的是head->pAppPrivate

那么具体的解码过程什么时候开始呢?
        OMX框架中所有的软解部分,最终都会通过OMX的消息机制走到SimpleSoftOMXComponent::onMessageReceived,函数的实现如下:

 void SimpleSoftOMXComponent::onMessageReceived(const sp<AMessage> &msg) {
    Mutex::Autolock autoLock(mLock);

    switch (msg->what()) {
        case kWhatSendCommand:
        {
            int32_t cmd, param;
            CHECK(msg->findInt32("cmd", &cmd));
            CHECK(msg->findInt32("param", &param));

            onSendCommand((OMX_COMMANDTYPE)cmd, (OMX_U32)param);
            break;
        }

        case kWhatEmptyThisBuffer:
        case kWhatFillThisBuffer:
        {
            OMX_BUFFERHEADERTYPE *header;
            CHECK(msg->findPointer("header", (void **)&header));

            CHECK(mState == OMX_StateExecuting && mTargetState == mState);

            bool found = false;
            for (size_t i = 0; i < mPorts.size(); ++i) {
                PortInfo *port = &mPorts.editItemAt(i);

                for (size_t j = 0; j < port->mBuffers.size(); ++j) {
                    BufferInfo *buffer = &port->mBuffers.editItemAt(j);

                    if (buffer->mHeader == header) {
                        CHECK(!buffer->mOwnedByUs);

                        buffer->mOwnedByUs = true;

                        CHECK((msg->what() == kWhatEmptyThisBuffer
                                    && port->mDef.eDir == OMX_DirInput)
                                || (port->mDef.eDir == OMX_DirOutput));

                        port->mQueue.push_back(buffer);
                        onQueueFilled(i);

                        found = true;
                        break;
                    }
                }
            }

            CHECK(found);
            break;
        }

        default:
            TRESPASS();
            break;
    }
}
显然这是一个消息的处理函数,对于 kWhatEmptyThisBufferkWhatFillThisBuffer这类消息都会进入到 onQueueFilled(i)函数,这个 onQueueFilled函数相当于是OMX软件编解码组件的一个入口,函数显示如下:

void SoftMPEG4::onQueueFilled(OMX_U32 portIndex) {
            …...
            Bool success = PVInitVideoDecoder(
                    mHandle, vol_data, &vol_size, 1, mWidth, mHeight, mode);

           …...
            MP4DecodingMode actualMode = PVGetDecBitstreamMode(mHandle);
           
            PVSetPostProcType((VideoDecControls *) mHandle, 0);
                notifyEmptyBufferDone(inHeader);
            …...
            PVSetReferenceYUV(mHandle, outHeader->pBuffer);

        if (PVDecodeVideoFrame(mHandle, &bitstream, &timestamp, &tmp,
            &useExtTimestamp,
            outHeader->pBuffer) != PV_TRUE) {

            notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
            mSignalledError = true;
            return;
        }
        …...
        notifyFillBufferDone(outHeader);

}

上面的函数只保留了几个关键的函数调用,可以看到在这个onQueueFilled函数中会通过一些类似与 PVDecodeVideoFrame进入到OMX 框架中软件编解码的部分。可以看到通过 PVDecodeVideoFrame之后的yuv数据会保存在outHeader->pBuffer字段中。然后通过notifyEmptyBufferDone(inHeader); 或者notifyFillBufferDone(outHeader);来向组件OMX外部通知已经从inPutPort消费了一个buffer或者已经向outPutPort填充了一个buffer
        简单说来,当notifyEmptyBufferDone的时候,OMX会记录已经被消费的buffer的索引,然后继续在该索引对应的bufferdrainInputBuffer,当notifyFillBufferDone时候最终会通过OMX的消息机制走到OMXCodec::on_message中进入case omx_message::FILL_BUFFER_DONE:主要工作是,记录这个已经填充的buffer的索引号,然后将这个索引号保存在

                mFilledBuffers.push_back(i);
                mBufferFilled.signal();
看到mBufferFilled这个Conditionsignal了,那么谁wait在这个Condition上呢?看上OMXCodec::read函数中我红色标注的部分。原来在OMXCodec::read函数的后半段中会一直
while (mState != ERROR && !mNoMoreOutputData && mFilledBuffers.empty()) {
        if ((err = waitForBufferFilled_l()) != OK) {
            return err;
        }
    }
       那么FILL_BUFFER_DONE之后,在OMXCodec::read函数中被返回给AwesomePlayermVideoBuffer就是在OMX框架中outPutPort上的buffer


通过上面的分析可以看到,虽然在OMX框架中 Input/OutPutPort上的buffer的生产和消费是异步,但是还是通过了一个Condition mBufferFilled来做同步。这个生产者消费者的问题,来用一个信号量做同步,还是比较好的。


抱歉!评论已关闭.