当应用层调用mediaplayer.start()的时候,在framework层对应的是在awesomeplayer中post一个mVideoEvent到TimedEventQueue中等待被调度。
当其被调度到的时候,会激活回调函数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或者来之网络的CacheSource。mBuffer数据填充完后赋值给调用函数的 srcBuffer,这样就相当于给 drainInputBuffer(BufferInfo *info)中的info填充了数据,然后回到drainInputBuffer函数,接着emptyBuffer。实际上这里的BufferInfo *info的数据填充过程还有一些细节需要弄清楚,BufferInfo的结构体各个字段的意义还有待进一步弄明白。 到了mOMX->emptyBuffer()基本就进入OMX框架了,无非是OMX那一套消息机制,之前已经说过,这里就不在赘述了。如果还有不清楚的话,其在openMax文档的时候我画的一张时序图。还有一点需要明确,在emptyBuffer的时候会将App Data copyToOMX,将来之Appcation的数据copy到OMX框架中,而在OMXNodeInstance::onMessage FILL_BUFFER_DONE的时候,会将数据从OMX框架中copy到Appcation空间,即copyFromOMX。Appcation<-- 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", ¶m)); 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; } } 显然这是一个消息的处理函数,对于 kWhatEmptyThisBuffer, kWhatFillThisBuffer这类消息都会进入到 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, ×tamp, &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的索引,然后继续在该索引对应的buffer上 drainInputBuffer,当notifyFillBufferDone时候最终会通过OMX的消息机制走到OMXCodec::on_message中进入case omx_message::FILL_BUFFER_DONE:主要工作是,记录这个已经填充的buffer的索引号,然后将这个索引号保存在 mFilledBuffers.push_back(i); mBufferFilled.signal(); 看到mBufferFilled这个Condition被signal了,那么谁wait在这个Condition上呢?看上OMXCodec::read函数中我红色标注的部分。原来在OMXCodec::read函数的后半段中会一直 while (mState != ERROR && !mNoMoreOutputData && mFilledBuffers.empty()) { if ((err = waitForBufferFilled_l()) != OK) { return err; } } 那么FILL_BUFFER_DONE之后,在OMXCodec::read函数中被返回给AwesomePlayer的mVideoBuffer就是在OMX框架中outPutPort上的buffer。 通过上面的分析可以看到,虽然在OMX框架中 Input/OutPutPort上的buffer的生产和消费是异步,但是还是通过了一个Condition mBufferFilled来做同步。这个生产者消费者的问题,来用一个信号量做同步,还是比较好的。