全新系列文章已更新:
- Android Media Framework - 开篇
- Android Media Framework(一)OpenMAX 框架简介
- Android Media Framework(二)OpenMAX 类型阅读与分析
- Android Media Framework(三)OpenMAX API阅读与分析
- Android Media Framework(四)Non-Tunneled组件的状态转换与buffer分配过程分析
- Android Media Framework(五)Tunnel Mode
- Android Media Framework(六)插件式编程与OMXStore
- Android Media Framework(七)MediaCodecService
- Android Media Framework(八)OMXNodeInstance - Ⅰ
- Android Media Framework(九)OMXNodeInstance - Ⅱ
- Android Media Framework(十)OMXNodeInstance - Ⅲ
之前的章节中我们解了 input buffer 是如何传递给 OMX 的,以及Output buffer 是如何分配并且注册给 OMX 的。这一节我们就来看ACodec是如何处理OMX的Callback的。
1、OMXNodeInstance Callback
这一节我们只大致记录Callback是如何传递给ACodec的。在之前的学习中我们了解到OMXNodeInstance中会有一个专门的线程来处理OMX的callback,这个线程的作用是把Callback按照时间顺序回传给ACodec。
CallbackDispatcher中维护了一个list,将消息回传给ACodec时并不是将list中的消息一条一条回传的,而是将list中所有的消息一次性回传,这也就是为什么ACodec处理OMXNodeInstance的消息时会有循环遍历。
在调用CodecObserver做消息上抛之前,会调用OMXNodeInstance::handleMessage 对消息做预处理,这里的预处里包括是否要将buffer做拷贝等等。
2、onOMXEmptyBufferDone
OMX使用完input buffer后,消息上抛到ACodec层,ACodec 会调用onOMXEmptyBufferDone再处理input buffer。
bool ACodec::BaseState::onOMXEmptyBufferDone(IOMX::buffer_id bufferID, int fenceFd) {
ALOGV("[%s] onOMXEmptyBufferDone %u",
mCodec->mComponentName.c_str(), bufferID);
BufferInfo *info = mCodec->findBufferByID(kPortIndexInput, bufferID);
BufferInfo::Status status = BufferInfo::getSafeStatus(info);
// 检查 Buffer 状态
if (status != BufferInfo::OWNED_BY_COMPONENT) {
ALOGE("Wrong ownership in EBD: %s(%d) buffer #%u", _asString(status), status, bufferID);
mCodec->dumpBuffers(kPortIndexInput);
if (fenceFd >= 0) {
::close(fenceFd);
}
return false;
}
// input buffer 回到 ACodec
info->mStatus = BufferInfo::OWNED_BY_US;
// input buffers cannot take fences, so wait for any fence now
(void)mCodec->waitForFence(fenceFd, "onOMXEmptyBufferDone");
fenceFd = -1;
// still save fence for completeness
info->setWriteFence(fenceFd, "onOMXEmptyBufferDone");
// We're in "store-metadata-in-buffers" mode, the underlying
// OMX component had access to data that's implicitly refcounted
// by this "MediaBuffer" object. Now that the OMX component has
// told us that it's done with the input buffer, we can decrement
// the mediaBuffer's reference count.
info->mData->meta()->setObject("mediaBufferHolder", sp<MediaBufferHolder>(nullptr));
// 获取当前的 PortMode
PortMode mode = getPortMode(kPortIndexInput);
switch (mode) {
case KEEP_BUFFERS:
break;
case RESUBMIT_BUFFERS:
postFillThisBuffer(info);
break;
case FREE_BUFFERS:
default:
ALOGE("SHOULD NOT REACH HERE: cannot free empty output buffers");
return false;
}
return true;
}
对input buffer的处理很简单,检查当前ACodec处在的状态并作出反应,如果处在 ExecutingState 则调用 postFillThisBuffer 将 Buffer 提交给 MediaCodec,同时清除 ACodec 存储的 mData。其他状态下则持有 input buffer 不会将其回传给 MediaCodec。
2、onOMXFillBufferDone
ACodec 处理 output buffer 的代码比较长,但是也不难,接下来就做分解学习:
首先有个 debug log,我们可以打开宏TRACK_BUFFER_TIMING来使用这部分内容,把input buffer写给 OMX 时会将pts以及调用时间做记录,在output buffer回传回来时,检查pts,打印出解码该帧消耗的时间。
#if TRACK_BUFFER_TIMING
index = mCodec->mBufferStats.indexOfKey(timeUs);
if (index >= 0) {
ACodec::BufferStats *stats = &mCodec->mBufferStats.editValueAt(index);
stats->mFillBufferDoneTimeUs = ALooper::GetNowUs();
ALOGI("frame PTS %lld: %lld",
timeUs,
stats->mFillBufferDoneTimeUs - stats->mEmptyBufferTimeUs);
mCodec->mBufferStats.removeItemsAt(index);
stats = NULL;
}
#endif
记录output BufferInfo是在第几帧被使用,mDequeueCounter可以看作是当前解码的帧数。
info->mDequeuedAt = ++mCodec->mDequeueCounter;
info->mStatus = BufferInfo::OWNED_BY_US;