之前的章节中我们解了 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(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;
2.1 Executing
在 Executing 状态下,会检查output buffer flag 和 size:
- output buffer size为0,flag 不是 OMX_BUFFERFLAG_EOS,说明没有解出有效数据,重新回传给 OMX 使用;
- output buffer size为0,flag 是 OMX_BUFFERFLAG_EOS,ACodec 已经收到 EOS,重新把 buffer 交给 OMX;
- 其他情况说明数据有效,或者是flag是 OMX_BUFFERFLAG_EOS,需要把output buffer回传给上层。
case RESUBMIT_BUFFERS: { // 如果output buffer长度为0,flag 不是 OMX_BUFFERFLAG_EOS // 如果output buffer长度为0,已将收到 EOS // 重新把 output buffer 提交给 OMX if (rangeLength == 0 && (!(flags & OMX_BUFFERFLAG_EOS) || mCodec->mPortEOS[kPortIndexOutput])) { ALOGV("[%s] calling fillBuffer %u", mCodec->mComponentName.c_str(), info->mBufferID); err = mCodec->fillBuffer(info); if (err != OK) { mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err)); return true; } break; } sp
buffer = info->mData; // ...... // 设定 pts buffer->meta()->setInt64("timeUs", timeUs); // 解除 ACodec 引用 info->mData.clear(); // 调用 drainThisBuffer mCodec->mBufferChannel->drainThisBuffer(info->mBufferID, flags); info->mStatus = BufferInfo::OWNED_BY_DOWNSTREAM; // 如果 flag 为 OMX_BUFFERFLAG_EOS,将PortEOS置为true if (flags & OMX_BUFFERFLAG_EOS) { ALOGV("[%s] saw output EOS", mCodec->mComponentName.c_str()); mCodec->mCallback->onEos(mCodec->mInputEOSResult); mCodec->mPortEOS[kPortIndexOutput] = true; } break; } 2.2 OutputPortSettingsChangedState
onOMXFillBufferDone 中有个比较特殊的case:FREE_BUFFERS,表示所有回传上来的 output buffer 都需要被释放。不难发现 FREE_BUFFERS 是属于 OutputPortSettingsChangedState 的,所以我们要先了解 OutputPortSettingsChangedState 这个状态是什么。
ACodec::BaseState::PortMode ACodec::OutputPortSettingsChangedState::getPortMode( OMX_U32 portIndex) { if (portIndex == kPortIndexOutput) { return FREE_BUFFERS; } CHECK_EQ(portIndex, (OMX_U32)kPortIndexInput); return RESUBMIT_BUFFERS; }
有以下两种情况:
- 在播放之前,我们并不知道输出output buffer格式;
- 播放过程中,码流格式发生变化,原先的output buffer并不适用新的输出;
出现这两种情况时需要重新分配output buffer,这个分配的过程ACodec的OutputPortSettingsChangedState中处理。
bool ACodec::ExecutingState::onOMXEvent( OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { switch (event) { case OMX_EventPortSettingsChanged: { // 检查是不是output端口发生变化 CHECK_EQ(data1, (OMX_U32)kPortIndexOutput); // 调用 ACodec onOutputFormatChanged mCodec->onOutputFormatChanged(); if (data2 == 0 || data2 == OMX_IndexParamPortDefinition) { mCodec->mMetadataBuffersToSubmit = 0; // 关闭 OMX output port的使用 CHECK_EQ(mCodec->mOMXNode->sendCommand( OMX_CommandPortDisable, kPortIndexOutput), (status_t)OK); // 释放没有被omx持有的output buffer mCodec->freeOutputBuffersNotOwnedByComponent(); // 切换到 OutputPortSettingsChangedState mCodec->changeState(mCodec->mOutputPortSettingsChangedState); } else if (data2 != OMX_IndexConfigCommonOutputCrop && data2 != OMX_IndexConfigAndroidIntraRefresh) { ALOGV("[%s] OMX_EventPortSettingsChanged 0x%08x", mCodec->mComponentName.c_str(), data2); } return true; } } }
Executing 状态下,如果ACodec收到OMX_EventPortSettingsChanged事件,则会进入端口设置改变的处理流程中:
- 首先检查设置改变的端口是否是 output 端口;
- 调用 onOutputFormatChanged 方法;
- OMX_CommandPortDisable 关闭 ouput port的使用;
- 释放没有被omx持有的output buffer;
- 切换到 OutputPortSettingsChangedState;
从这个流程中我们大致可以猜测,处理PortSettingsChanged事件,需要把所有的output buffer销毁。buffer可能有两种状态,一种是被OMX持有,另一种是被上层持有(ACodec、MediaCodec),这两种状态的销毁流程是不太一样的,被OMX持有的output buffer需要回到上层才能被销毁。
status_t ACodec::freeOutputBuffersNotOwnedByComponent() { status_t err = OK; for (size_t i = mBuffers[kPortIndexOutput].size(); i > 0;) { i--; BufferInfo *info = &mBuffers[kPortIndexOutput].editItemAt(i); // At this time some buffers may still be with the component // or being drained. if (info->mStatus != BufferInfo::OWNED_BY_COMPONENT && info->mStatus != BufferInfo::OWNED_BY_DOWNSTREAM) { status_t err2 = freeBuffer(kPortIndexOutput, i); if (err == OK) { err = err2; } } } return err; }
销毁buffer在freeBuffer中完成:
status_t ACodec::freeBuffer(OMX_U32 portIndex, size_t i) { BufferInfo *info = &mBuffers[portIndex].editItemAt(i); status_t err = OK; // there should not be any fences in the metadata if (mPortMode[portIndex] == IOMX::kPortModeDynamicANWBuffer && info->mCodecData != NULL && info->mCodecData->size() >= sizeof(VideoNativeMetadata)) { int fenceFd = ((VideoNativeMetadata *)info->mCodecData->base())->nFenceFd; if (fenceFd >= 0) { ALOGW("unreleased fence (%d) in %s metadata buffer %zu", fenceFd, portIndex == kPortIndexInput ? "input" : "output", i); } } switch (info->mStatus) { case BufferInfo::OWNED_BY_US: if (portIndex == kPortIndexOutput && mNativeWindow != NULL) { (void)cancelBufferToNativeWindow(info); } FALLTHROUGH_INTENDED; case BufferInfo::OWNED_BY_NATIVE_WINDOW: err = mOMXNode->freeBuffer(portIndex, info->mBufferID); break; default: ALOGE("trying to free buffer not owned by us or ANW (%d)", info->mStatus); err = FAILED_TRANSACTION; break; } if (info->mFenceFd >= 0) { ::close(info->mFenceFd); } if (portIndex == kPortIndexOutput) { mRenderTracker.untrackFrame(info->mRenderInfo, i); info->mRenderInfo = NULL; } // remove buffer even if mOMXNode->freeBuffer fails mBuffers[portIndex].removeAt(i); return err; }