您的位置:首页 > 移动开发 > Android开发

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

2013-01-05 11:40 627 查看
当应用层调用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来做同步。这个生产者消费者的问题,来用一个信号量做同步,还是比较好的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐