这一节我们一起来了解 ACodec 是如何通过 configureCodec 方法配置 OMX 组件的,因为 configureCodec 代码比较长,所以我们会把代码进行拆分来了解。
ps:这部分的代码我们先跳过 encoder 的流程。
先来看函数入参,第一个参数 mime,第二个参数为 AMessage,不过看 onConfigureComponent 代码我们就可以知道 mime 是来自于 msg 的,所以这里边的信息会有重复。
cpp
status_t ACodec::configureCodec(const char *mime, const sp<AMessage> &msg)
进入函数体内,首先干了3件事情:
cpp
int32_t encoder;
if (!msg->findInt32("encoder", &encoder)) {
encoder = false;
}
// 创建空白的input / output format message
sp<AMessage> inputFormat = new AMessage;
sp<AMessage> outputFormat = new AMessage;
mConfigFormat = msg;
mIsEncoder = encoder;
mIsVideo = !strncasecmp(mime, "video/", 6);
mIsImage = !strncasecmp(mime, "image/", 6);
// 初始化 port mode
mPortMode[kPortIndexInput] = IOMX::kPortModePresetByteBuffer;
mPortMode[kPortIndexOutput] = IOMX::kPortModePresetByteBuffer;
- 判断当前的组件是 encoder 还是 decoder;
- 判断组件是 audio 还是 video;
- 初始化 input 和 ouput format 以及 port mode;
在这里 input format 和传入参数 msg 中的内容有一些区别,msg 中的信息都是由 extractor 解析出来的,input format 中除了有这些信息外,还存储有用于配置 OMX 组件的一些信息;output format 中存储的是 OMX 组件回传给上层的输出格式信息。
接下来有一个很重要的内容就是 Port Mode,每个组件会有两个端口(input / output Port),这里的 mode 指的就是对这两个端口的定义,影响的是在端口中传输的 buffer 的类型。
PortMode
定义位于 frameworks/av/media/libmedia/include/media/IOMX.h
之所以把 PortMode 放在这个文件中,是因为它只用于 ACodec 和 OMXNode 当中,OMXNode 收到 ACodec 设定下来的 mode 之后会做相关处理之后再传送给 OMX 组件。
PortMode 分为两组:
cpp
enum PortMode {
kPortModePresetStart = 0,
kPortModePresetByteBuffer,
kPortModePresetANWBuffer,
kPortModePresetSecureBuffer,
kPortModePresetEnd,
kPortModeDynamicStart = 100,
kPortModeDynamicANWBuffer, // uses metadata mode kMetadataBufferTypeANWBuffer
// or kMetadataBufferTypeGrallocSource
kPortModeDynamicNativeHandle, // uses metadata mode kMetadataBufferTypeNativeHandleSource
kPortModeDynamicEnd,
};
一组以 Preset 前缀,另一组以 Dynamic 作为前缀。他们两个的区别在于,Preset 表示端口内的 buffer 在启动前已经被预先设定好了,在编解码组件运行过程中这些 buffer 不会发生变化
;Dynamic 则表示动态,意思就是组件运行过程中,我们使用的 buffer 可能会发生动态变化,有一些 buffer 可能被弃用,也有一些 buffer 会被新加入使用;这里对部分 PortMode 类型进行描述:
kPortModePresetByteBuffer
:使用最普通的 buffer,我们可以很轻松访问到 buffer 中的内容;kPortModePresetANWBuffer
:使用预先设定的 Native Window Buffer,我们将无法直接访问这块 buffer 中的数据;kPortModePresetSecureBuffer
:使用预先设定的 Secure Buffer,我们无法直接访问 buffer 中的内容;kPortModeDynamicANWBuffer
:使用动态的 Native Window Buffer,我们将无法直接访问这块 buffer 中的数据;kPortModeDynamicNativeHandle
:使用动态的 Native Buffer Handle,buffer 以 handle 的形式回传上来,我们将无法直接访问 buffer 中的数据;
具体这些 Mode 会在什么情况下使用,我们在后面会看到。
cpp
status_t ACodec::setComponentRole(
bool isEncoder, const char *mime) {
const char *role = GetComponentRole(isEncoder, mime);
if (role == NULL) {
return BAD_VALUE;
}
status_t err = SetComponentRole(mOMXNode, role);
if (err != OK) {
ALOGW("[%s] Failed to set standard component role '%s'.",
mComponentName.c_str(), role);
}
return err;
}
接下来会调用 setComponentRole 方法,首先来讲我理解的为什么要调用这个方法:我们实现的 OMX 组件可能共享的是一套流程,也就是各个组件 lib 可能是链接到同一个lib当中,那这里就会有一个问题,每当我们调用 getHandle 创建一个句柄时,组件并不知道我们要对什么格式的数据进行处理,也不知道是做编码还是做解码,所以上层需要设定相关参数通知 OMX 组件它需要走什么流程。
这个方法还有一个作用,它内部有个 GetComponentRole 方法,会根据传进来的 mime type 获取对应的 Role,如果这里没有找到对应的 role,则会发生 error,因此如果要支持某个格式的播放,则必须要修改 GetComponentRole 中使用到的一个数组 kMimeToRole
,我们这里就不再展开了。
这里 SetComponentRole 是如何将参数设定下去的也不做过多的解释,主要流程是创建一个参数,初始化参数,设定参数内容,调用 setParameter 传递参数。
cpp
status_t SetComponentRole(const sp<IOMXNode> &omxNode, const char *role) {
OMX_PARAM_COMPONENTROLETYPE roleParams;
InitOMXParams(&roleParams);
strncpy((char *)roleParams.cRole,
role, OMX_MAX_STRINGNAME_SIZE - 1);
roleParams.cRole[OMX_MAX_STRINGNAME_SIZE - 1] = '\0';
return omxNode->setParameter(
OMX_IndexParamStandardComponentRole,
&roleParams, sizeof(roleParams));
}
我们这里先跳过 encoder 的流程,所以接下来会跳过一部分代码。
cpp
int32_t lowLatency = 0;
if (msg->findInt32("low-latency", &lowLatency)) {
err = setLowLatency(lowLatency);
if (err != OK) {
return err;
}
}
判断并设定 OMX 组件是否打开低延迟的模式,我理解的 low-latency
可能是 OMX 组件低缓冲快速解码。
cpp
// 查找是否有 native window(surface)设定下来
sp<RefBase> obj;
bool haveNativeWindow = msg->findObject("native-window", &obj)
&& obj != NULL && mIsVideo && !encoder;
mUsingNativeWindow = haveNativeWindow;
if (mIsVideo && !encoder) {
inputFormat->setInt32("adaptive-playback", false);
int32_t usageProtected;
// 如果 format 中有设定 protect 信息
if (msg->findInt32("protected", &usageProtected) && usageProtected) {
if (!haveNativeWindow) {
ALOGE("protected output buffers must be sent to an ANativeWindow");
return PERMISSION_DENIED;
}
// 设定相关flag
mFlags |= kFlagIsGrallocUsageProtected;
mFlags |= kFlagPushBlankBuffersToNativeWindowOnShutdown;
}
}
// 如果是 secure 组件
if (mFlags & kFlagIsSecure) {
// use native_handles for secure input buffers
err = setPortMode(kPortIndexInput, IOMX::kPortModePresetSecureBuffer);
if (err != OK) {
ALOGI("falling back to non-native_handles");
setPortMode(kPortIndexInput, IOMX::kPortModePresetByteBuffer);
err = OK; // ignore error for now
}
OMX_INDEXTYPE index;
if (mOMXNode->getExtensionIndex(
"OMX.google.android.index.preregisterMetadataBuffers", &index) == OK) {
OMX_CONFIG_BOOLEANTYPE param;
InitOMXParams(¶m);
param.bEnabled = OMX_FALSE;
if (mOMXNode->getParameter(index, ¶m, sizeof(param)) == OK) {
if (param.bEnabled == OMX_TRUE) {
mFlags |= kFlagPreregisterMetadataBuffers;
}
}
}
}