前面两节我们了解了 ACodec 的创建及配置流程,配置完成后 ACodec 进入了 LoadedState,这一节开始将会了解 ACodec 的启动过程。
调用 ACodec::initiateStart 方法发出的 kWhatStart 消息将有 LoadedState 状态来处理,这个方法会向 OMX 组件发送命令OMX_CommandStateSet
,将组件的状态设定为 OMX_StateIdle
,之后将 ACodec 的状态切换到中间等待状态 LoadedToIdleState
。
void ACodec::LoadedState::onStart() {
ALOGV("onStart");
status_t err = mCodec->mOMXNode->sendCommand(OMX_CommandStateSet, OMX_StateIdle);
if (err != OK) {
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
} else {
mCodec->changeState(mCodec->mLoadedToIdleState);
}
}
切换状态时调用 LoadedToIdleState 的 stateEntered 方法,来为 OMX 组件分配 buffer,这是很关键的一步。
void ACodec::LoadedToIdleState::stateEntered() {
ALOGV("[%s] Now Loaded->Idle", mCodec->mComponentName.c_str());
status_t err;
if ((err = allocateBuffers()) != OK) {
ALOGE("Failed to allocate buffers after transitioning to IDLE state "
"(error 0x%08x)",
err);
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
mCodec->mOMXNode->sendCommand(
OMX_CommandStateSet, OMX_StateLoaded);
if (mCodec->allYourBuffersAreBelongToUs(kPortIndexInput)) {
mCodec->freeBuffersOnPort(kPortIndexInput);
}
if (mCodec->allYourBuffersAreBelongToUs(kPortIndexOutput)) {
mCodec->freeBuffersOnPort(kPortIndexOutput);
}
mCodec->changeState(mCodec->mLoadedState);
}
}
stateEntered 中主要调用了 allocateBuffers 方法,如果返回结果有问题,那么会将状态回滚到 LoadedState。
status_t ACodec::LoadedToIdleState::allocateBuffers() {
status_t err = mCodec->allocateBuffersOnPort(kPortIndexInput);
if (err != OK) {
return err;
}
err = mCodec->allocateBuffersOnPort(kPortIndexOutput);
if (err != OK) {
return err;
}
mCodec->mCallback->onStartCompleted();
return OK;
}
allocateBuffers 中将 buffer 分配完成后,就会调用 callback 通知 MediaCodec 完成阻塞调用了。我们上面说到将 OMX 组件状态设置为 OMX_StateIdle,这个状态下,OMX 组件处理这个消息时应该是处于一个阻塞的状态,阻塞是在等待上层 buffer 分配完成,一旦完成后就会向 ACodec 发送一条消息,表示事件处理完成了(buffer准备完成),这时候 ACodec 将会再向 OMX 组件发送状态设置命令,将组件状态设置为 OMX_StateExecuting
,组件就正式开始工作了。
bool ACodec::LoadedToIdleState::onOMXEvent(
OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) {
switch (event) {
case OMX_EventCmdComplete:
{
status_t err = OK;
if (data1 != (OMX_U32)OMX_CommandStateSet
|| data2 != (OMX_U32)OMX_StateIdle) {
ALOGE("Unexpected command completion in LoadedToIdleState: %s(%u) %s(%u)",
asString((OMX_COMMANDTYPE)data1), data1,
asString((OMX_STATETYPE)data2), data2);
err = FAILED_TRANSACTION;
}
if (err == OK) {
err = mCodec->mOMXNode->sendCommand(
OMX_CommandStateSet, OMX_StateExecuting);
}
if (err != OK) {
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
} else {
mCodec->changeState(mCodec->mIdleToExecutingState);
}
return true;
}
default:
return BaseState::onOMXEvent(event, data1, data2);
}
}
上面这段主要是要理解,OMX组件在处理 OMX_StateIdle
这条命令时会处在一个阻塞的状态
。
接下来我们就要一起看 buffer 是如何分配的,如果已经了解我们上一节看的 Port Mode,那么这部分还是很简单的。
代码比较长,我们把代码分成两部分来看:
有native window的情况下分配output buffer;
对 input buffer ,以及没有 native window 时的 output buffer 进行分配;
首先我们来看第一部分:
// 1、在有native window的情况下分配output buffer
if (mNativeWindow != NULL && portIndex == kPortIndexOutput) {
if (storingMetadataInDecodedBuffers()) {
err = allocateOutputMetadataBuffers();
} else {
err = allocateOutputBuffersFromNativeWindow();
}
}
我们在上一篇中了解到有native window 时,output mode会有两种情况,一种是 tunnel mode;另一种是 kPortModeDynamicANWBuffer,也就是所谓的 MetaData mode,这里我们先看这种模式。
status_t ACodec::allocateOutputMetadataBuffers() {
CHECK(storingMetadataInDecodedBuffers());
// 1、调用方法获取 native window 可用的 output buffer 数量以及大小
OMX_U32 bufferCount, bufferSize, minUndequeuedBuffers;
status_t err = configureOutputBuffersFromNativeWindow(
&bufferCount, &bufferSize, &minUndequeuedBuffers,
mFlags & kFlagPreregisterMetadataBuffers /* preregister */);
if (err != OK)
return err;
mNumUndequeuedBuffers = minUndequeuedBuffers;
ALOGV("[%s] Allocating %u meta buffers on output port",
mComponentName.c_str(), bufferCount);
// 2、创建 对应数量的 BufferInfo
for (OMX_U32 i = 0; i < bufferCount; i++) {
BufferInfo info;
info.mStatus = BufferInfo::OWNED_BY_NATIVE_WINDOW;
info.mFenceFd = -1;
info.mRenderInfo = NULL;
info.mGraphicBuffer = NULL;
info.mNewGraphicBuffer = false;
info.mDequeuedAt = mDequeueCounter;
// 3、创建一个 MediaCodecBuffer
info.mData = new MediaCodecBuffer(mOutputFormat, new ABuffer(bufferSize));
// Initialize fence fd to -1 to avoid warning in freeBuffer().
((VideoNativeMetadata *)info.mData->base())->nFenceFd = -1;
info.mCodecData = info.mData;
// 4、调用 useBuffer 让 OMX 组件使用一块空的 buffer,并且回传 ID
err = mOMXNode->useBuffer(kPortIndexOutput, OMXBuffer::sPreset, &info.mBufferID);
mBuffers[kPortIndexOutput].push(info);
ALOGV("[%s] allocated meta buffer with ID %u",
mComponentName.c_str(), info.mBufferID);
}
// 5、计算需要提交的 output buffer 数量
mMetadataBuffersToSubmit = bufferCount - minUndequeuedBuffers;
return err;
}
这个方法大致做了以下5个事情:
status_t ACodec::configureOutputBuffersFromNativeWindow(
OMX_U32 *bufferCount, OMX_U32 *bufferSize,
OMX_U32 *minUndequeuedBuffers, bool preregister) {
OMX_PARAM_PORTDEFINITIONTYPE def;
InitOMXParams(&def);
def.nPortIndex = kPortIndexOutput;
// 获取 OMX 组件定义的 output port 的定义,定义中会有 output buffer 的数量
status_t err = mOMXNode->getParameter(
OMX_IndexParamPortDefinition, &def, sizeof(def));
if (err == OK) {
err = setupNativeWindowSizeFormatAndUsage(
mNativeWindow.get(), &mNativeWindowUsageBits,
preregister && !mTunneled /* reconnect */);
}
if (err != OK) {
mNativeWindowUsageBits = 0;
return err;
}
// 设置从 nativewindow 中获取buffer这个动作为阻塞的
static_cast<Surface *>(mNativeWindow.get())->setDequeueTimeout(-1);
// Exits here for tunneled video playback codecs -- i.e. skips native window
// buffer allocation step as this is managed by the tunneled OMX omponent
// itself and explicitly sets def.nBufferCountActual to 0.
// 如果是 tunnel mode,那么端口的 buffer 数量为0,不需要从上层获取 output buffer
if (mTunneled) {
ALOGV("Tunneled Playback: skipping native window buffer allocation.");
def.nBufferCountActual = 0;
err = mOMXNode->setParameter(
OMX_IndexParamPortDefinition, &def, sizeof(def));
*minUndequeuedBuffers = 0;
*bufferCount = 0;
*bufferSize = 0;
return err;
}
// 从 native window 获取最小的还未出队列的 buffer 数量
*minUndequeuedBuffers = 0;
err = mNativeWindow->query(
mNativeWindow.get(), NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
(int *)minUndequeuedBuffers);
if (err != 0) {
ALOGE("NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS query failed: %s (%d)",
strerror(-err), -err);
return err;
}
// 重新计算OMX 组件上 output buffer 的数量
// FIXME: assume that surface is controlled by app (native window
// returns the number for the case when surface is not controlled by app)
// FIXME2: This means that minUndeqeueudBufs can be 1 larger than reported
// For now, try to allocate 1 more buffer, but don't fail if unsuccessful
// Use conservative allocation while also trying to reduce starvation
//
// 1. allocate at least nBufferCountMin + minUndequeuedBuffers - that is the
// minimum needed for the consumer to be able to work
// 2. try to allocate two (2) additional buffers to reduce starvation from
// the consumer
// plus an extra buffer to account for incorrect minUndequeuedBufs
for (OMX_U32 extraBuffers = 2 + 1; /* condition inside loop */; extraBuffers--) {
// 尝试将output buffer 的数量设置为端口所需的最小数量 + nativewindow最小未出队列的buffer数量 + 3
OMX_U32 newBufferCount =
def.nBufferCountMin + *minUndequeuedBuffers + extraBuffers;
def.nBufferCountActual = newBufferCount;
err = mOMXNode->setParameter(
OMX_IndexParamPortDefinition, &def, sizeof(def));
if (err == OK) {
*minUndequeuedBuffers += extraBuffers;
break;
}
ALOGW("[%s] setting nBufferCountActual to %u failed: %d",
mComponentName.c_str(), newBufferCount, err);
/* exit condition */
if (extraBuffers == 0) {
return err;
}
}
// 设置 native window 的buffer数量
err = native_window_set_buffer_count(
mNativeWindow.get(), def.nBufferCountActual);
if (err != 0) {
ALOGE("native_window_set_buffer_count failed: %s (%d)", strerror(-err),
-err);
return err;
}
// 设置 buffercount 和 buffersize
*bufferCount = def.nBufferCountActual;
*bufferSize = def.nBufferSize;
return err;
}
由于不了解 Graphic 相关的内容,所以这部分只能边看边猜,以下是我自己的理解,可能有误:
这里对 nBufferCountActual
(真实使用的buffer数量)的计算比较令人疑惑,上面的代码中有一个循环,会尝试将 nBufferCountMin
(最小 buffer 数量)+ minUndequeuedBuffers
+ extra
作为真实值,并且尝试设定给组件,只要这个值没有超过最大值就可以成功设定。
这里的 minUndequeuedBuffers
代表什么意思呢?上文中的 mMetadataBuffersToSubmit = bufferCount - minUndequeuedBuffers
又是代表什么意思呢?
我的理解是支持OMX运行的最少的output buffer 数量为 nBufferCountMin
,那么就先分配这么多个(指的就是mMetadataBuffersToSubmit
),如果转不过来要请求新的了,再去 minUndequeuedBuffers
个中获取新的;意思可能是上来不是用全力,而是随着需求的改变来改变native window分配的buffer数量。