全新系列文章已更新:
- 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 - Ⅲ
- Android Media Framework(十一)OMXNodeInstance - Ⅳ
- Android Media Framework(十二)OMXNodeInstance - Ⅴ
- Android Media Framework(十三)ACodec - Ⅰ
- Android Media Framework(十四)ACodec - Ⅱ
- Android Media Framework(十五)ACodec - Ⅲ
- Android Media Framework(十六)ACodec - Ⅳ
拖了好久都没有更新,前面写的东西都有些忘了,回过头来再看之前写的内容,觉得有很多地方写的不好,或者说现在又有了新的理解,想要重新修改但是需要修改的内容太多,因此决定按照当前的思路把剩余的内容写完。
Android ACodec OpenMax部分还有OutputPortSettingsChangedState、Flush、Release、output buffer的处理这四块内容,写完了之后可能会花时间重新再阅读一遍,整理出更系统的内容。加油!
接前面内容,之前我们已经了解了MediaCodec如何启动,ACodec的input/output buffer是如何分配的,以及OMXNodeInstance是如何发消息。接下来将会学习解码启动后正常运转过程的内容。
这一节就来看OutputPortSettingsChangedState这个状态。
在之前的学习中我们有提到过,播放过程中码流的分辨率发生了变化,这时候应该怎么办呢?
1、OMX_EventPortSettingsChanged
现在的decoder一般都会支持 Adaptive Playback,所谓自适应播放指的是两部分内容:
- 起播时并不一定要设定准确的宽高信息,解码器可以自己解出码流的宽高信息,并且做出调整;
- 播放过程中码流的分辨率发生变化,播放器可以自适应调整;
这里说的调整指的就是自己调整output buffer size,不需要我们重启播放器。buffer size调整的过程涉及到原有buffer的释放,以及新的buffer的分配,所以需要做的内容会比较多。
bool ACodec::ExecutingState::onOMXEvent(
OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) {
switch (event) {
case OMX_EventPortSettingsChanged:
{
// 检查是否是Output port
CHECK_EQ(data1, (OMX_U32)kPortIndexOutput);
// 获取新的output format
mCodec->onOutputFormatChanged();
if (data2 == 0 || data2 == OMX_IndexParamPortDefinition) {
// 将待提交的 buffer 数量设置为 0
mCodec->mMetadataBuffersToSubmit = 0;
// 禁用 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;
}
default:
return BaseState::onOMXEvent(event, data1, data2);
}
}
- ExecutingState状态下,收到OMX_EventPortSettingsChanged消息后,ACodec首先从组件中获取到新的OutputFormat,onOutputFormatChanged方法我们这里不做展开。
- 接着将mMetadataBuffersToSubmit置为0,这个操作是让起播时收到OMX_EventPortSettingsChanged,向组件写input 数据时不要再传递output buffer。
- 禁用OMX组件的 output port。
- 释放所有没有送给 OMX 组件的output buffer。
- ACodec进入到OutputPortSettingsChangedState状态。
这里要们先看一下第四点freeOutputBuffersNotOwnedByComponent:
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前会先检查BufferInfo的状态,如果BufferInfo不属于OMX组件,或者不是在等待渲染的状态,这时候需要调用freeBuffer。
我们再来回顾一下,OutputBuffer 可能有四种状态:
- BufferInfo::OWNED_BY_US
- BufferInfo::OWNED_BY_COMPONENT
- BufferInfo::OWNED_BY_DOWNSTREAM
- BufferInfo::OWNED_BY_NATIVE_WINDOW
意思也就是,当OutputBuffer归属于ACodec或者是nativewindow时可以释放。
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;
}
对于OWNED_BY_US和OWNED_BY_NATIVE_WINDOW两种状态,freebuffer需要做的内容有一点点不一样,OWNED_BY_US需要多做一个cancelBufferToNativeWindow,可以理解为取消使用的意思,将graphic buffer返回给nativewindow,graphic buffer返回之后再调用freeBuffer,释放OMX组件所持有的graphic buffer了。
到这,我们要先想想OMX组件什么时候会发送 OMX_EventPortSettingsChanged 事件回来?我理解的是,前一个序列的output全部回传给了上层,下一个序列回传之前会送出事件,处理完成之后再把新的序列的output填充回传。
如我们上文所说的,output buffer有四种状态,OWNED_BY_NATIVE_WINDOW指的是buffer还未分配,或者已经送到nativewindow等待渲染;OWNED_BY_US表示buffer由ACodec持有,有两种可能,一种是buffer处在ACodec处理的中间状态,还有一种是buffer不需要被处理,由ACodec持有。这两种状态下,output buffer确定不会被使用,所以可以先release。
OWNED_BY_DOWNSTREAM状态下的buffer表示回传给上层做Avsync,还未render,等待render完成后会release。
OWNED_BY_COMPONENT状态下的buffer需要等OMX组件把buffer id回传,确认OMX组件不再使用再release。