Android 13 - Media框架(21)- ACodec(三)

全新系列文章已更新:


这一节我们一起来了解 ACodec 是如何通过 configureCodec 方法配置 OMX 组件的,因为 configureCodec 代码比较长,所以我们会把代码进行拆分来了解。
ps:这部分的代码我们先跳过 encoder 的流程。

先来看函数入参,第一个参数 mime,第二个参数为 AMessage,不过看 onConfigureComponent 代码我们就可以知道 mime 是来自于 msg 的,所以这里边的信息会有重复。

status_t ACodec::configureCodec(const char *mime, const sp<AMessage> &msg) 

进入函数体内,首先干了3件事情:

    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;
  1. 判断当前的组件是 encoder 还是 decoder;
  2. 判断组件是 audio 还是 video;
  3. 初始化 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 分为两组:

    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 会在什么情况下使用,我们在后面会看到。

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 传递参数。

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 的流程,所以接下来会跳过一部分代码。

    int32_t lowLatency = 0;
    if (msg->findInt32("low-latency", &lowLatency)) {
        err = setLowLatency(lowLatency);
        if (err != OK) {
            return err;
        }
    }

判断并设定 OMX 组件是否打开低延迟的模式,我理解的 low-latency 可能是 OMX 组件低缓冲快速解码。

	// 查找是否有 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(&param);
            param.bEnabled = OMX_FALSE;
            if (mOMXNode->getParameter(index, &param, sizeof(param)) == OK) {
                if (param.bEnabled == OMX_TRUE) {
                    mFlags |= kFlagPreregisterMetadataBuffers;
                }
            }
        }
    }

以上这段代码就开始使用到我们上文讲到的 PortMode 了。如果使用的是 secure 组件,那么 input port mode 会被设定为 kPortModePresetSecureBuffer,这种情况下,input buffer将会使用预设的 secure buffer,我们在向 input buffer 写入数据的时候会与普通的memcpy有一些不同,这一点我们后续再看;如果port mode 设定失败,那么将尝试使用 kPortModePresetByteBuffer mode,也就是所谓的普通 buffer。

    if (haveNativeWindow) {
        sp<ANativeWindow> nativeWindow =
            static_cast<ANativeWindow *>(static_cast<Surface *>(obj.get()));

        int32_t tunneled;
        if (msg->findInt32("feature-tunneled-playback", &tunneled) &&
            tunneled != 0) {
            ALOGI("Configuring TUNNELED video playback.");
            mTunneled = true;

            int32_t audioHwSync = 0;
            if (!msg->findInt32("audio-hw-sync", &audioHwSync)) {
                ALOGW("No Audio HW Sync provided for video tunnel");
            }
            err = configureTunneledVideoPlayback(audioHwSync, nativeWindow);
            if (err != OK) {
                ALOGE("configureTunneledVideoPlayback(%d,%p) failed!",
                        audioHwSync, nativeWindow.get());
                return err;
            }

            int32_t maxWidth = 0, maxHeight = 0;
            if (msg->findInt32("max-width", &maxWidth) &&
                    msg->findInt32("max-height", &maxHeight)) {

                err = mOMXNode->prepareForAdaptivePlayback(
                        kPortIndexOutput, OMX_TRUE, maxWidth, maxHeight);
                if (err != OK) {
                    ALOGW("[%s] prepareForAdaptivePlayback failed w/ err %d",
                            mComponentName.c_str(), err);
                    // allow failure
                    err = OK;
                } else {
                    inputFormat->setInt32("max-width", maxWidth);
                    inputFormat->setInt32("max-height", maxHeight);
                    inputFormat->setInt32("adaptive-playback", true);
                }
            }
        } 

视频解码完成,如果要显示(渲染)就需要一个 Native Window,可以理解为一个窗口。渲染有两种AvSync 方式,一种是利用硬件同步,另一种是软件同步。硬件同步也就是上面代码中的 tunnel mode,需要从 Audio HAL 获取一个 hw sync id,然后把该 id 传递给 OMX 组件,创建出一个 sideband Handle,最后将该 handle 与 native window绑定,这样就完成了硬件同步的设定,具体硬件同步是如何实现的,暂时还没有做了解。

上面所说的绑定是通过调用 configureTunneledVideoPlayback 来完成,里面有两步内容,一是创建 handle,而是将handle 与 native window 绑定,这里不做展开。

这里有一点内容我们需要提前做了解,由于 Tunnel Mode 的 AvSync 不需要上层再做介入,所以 output buffer 将不再会回流到上层。这种模式下 ouput port mode将没有意义。


关注公众号《青山渺渺》阅读完整内容; 如有问题可在公众号后台私信,也可进入音视频开发技术分享群一起讨论!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青山渺渺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值