ACodec之所以复杂,主要是因为状态太多。在上一篇文章中,我们学习了在ExecutingState下对buffer的处理。ExecutingState可能会切换到OutputPortSettingsChangedState、FlushingState,或者当组件被释放时,进入UninitializedState。接下来,我们将探讨这些情况下的buffer处理流程。
1、OMX_EventPortSettingsChanged
之前我们提到解码器支持Adaptive Playback的方式有两种,一种是分配足够的大的output buffer,另一种是发送Event给上层重新分配output buffer,这一节我们来讲一讲第二种方式。
当码流的分辨率发生变化时,解码器会发送OMX_EventPortSettingsChanged Event给上层,ACodec在Executing状态下会做如下处理:
case OMX_EventPortSettingsChanged:
{
CHECK_EQ(data1, (OMX_U32)kPortIndexOutput);
// 获取output format
mCodec->onOutputFormatChanged();
if (data2 == 0 || data2 == OMX_IndexParamPortDefinition) {
mCodec->mMetadataBuffersToSubmit = 0;
// 禁用output port
CHECK_EQ(mCodec->mOMXNode->sendCommand(
OMX_CommandPortDisable, kPortIndexOutput),
(status_t)OK);
// 释放不属于组件的output buffer
mCodec->freeOutputBuffersNotOwnedByComponent();
// 切换到OutputPortSettingsChangedState
mCodec->changeState(mCodec->mOutputPortSettingsChangedState);
}
return true;
}
- 调用onOutputFormatChanged获取输出端口的format信息;
- 发送命令禁用组件的输出端口;
- 调用freeOutputBuffersNotOwnedByComponent释放部分output buffer;
- 切换状态到OutputPortSettingsChangedState。
想要了解这段代码的含义,首先我们要搞清楚此时各个模块持有output buffer的情况:
从上图我们可以知道,可能有三个模块持有buffer:
- MediaCodec:它指代的是整个上层应用,持有output buffer一般是在做Avsync;
- ACodec:在正常工作过程中,ACodec是不会持有input/output buffer的,所以触发OMX_EventPortSettingsChanged事件时不会有buffer被ACodec持有;
- NativeWindow:表示buffer已经被送去渲染了;
- OMX Component:output buffer正在组件中等待填充;
发送命令禁用组件的输出端口,命令是非阻塞执行的,禁用组件输出端口需要将组件持有的buffer送还给ACodec,当所有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);
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;
}
freeOutputBuffersNotOwnedByComponent翻译过来是释放没有被组件持有的buffer,但是从方法实现来看,除了被组件持有的buffer不会被释放,被上层持有的buffer也不会被释放。
事件触发时,OWNED_BY_DOWNSTREAM状态的buffer是上一个视频序列的最后几帧,我们要等渲染完成才能释放它们,否则画面分辨率变化时可能会有跳帧的情况。
status_t ACodec::freeBuffer(OMX_U32 portIndex, size_t i) {
BufferInfo *info = &mBuffers[portIndex].editItemAt(i);
status_t err = OK;
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;
}
mBuffers[portIndex].removeAt(i);
return err;
}
调用ACodec::freeBuffer时首先会检查BufferInfo记录的buffer状态,此处调用时buffer状态为OWNED_BY_NATIVE_WINDOW,因此直接调用OMXNode的freeBuffer方法释放output buffer。
2、OutputPortSettingsChangedState
3、FlushingState
4、signalResume
5、initiateShutdown
5、其他
我们在文章一开始说到,“ACodec之所以复杂,主要是因为状态太多”,上面我们只分析了Executing状态时组件的停止过程,实际上可能会在flush状态下调用initiateShutdown,也有可能在Loaded状态下调用initiateShutdown。类似的,也可能可能在OutputPortSettingsChangedState调用flush方法。ACodec对所有可能的情况都做了处理,所以代码看起来会显得复杂,但是实际上只要理清不同状态切换时需要如何处理buffer就可以了。
这是OpenMAX框架的最后一篇分析,后续将会开始全新的Codec2框架解析,以及Player和MediaExtractor分析,写作顺序如何安排还需要再做考虑。
关注公众号《青山渺渺》阅读全文