• 简单OpenSL ES学习


    OpenSL ES

    简单来说OpenSL ES是一个嵌入式跨平台免费的音频处理库。 所以它不是Android特有的。它从PC端的整出来一个小一些的第三方库专门来给移动端使用,跨平台、无授权费,针对嵌入式系统精心优化的硬件音频加速 API。

    最后一句话就是:我们商业应用用的就是它,所以要学习了解它。

    据说人家是C语音,兼容C++,然后以面对对象的思想设计的。

    可以播放PCM的一个库。

    Objects和Interfaces

    我们要想使用一个对象,必须创建这个对象,然后通过这个对象拿到它提供的接口,最后再通过接口提供的函数去执行。

    设计的所有最顶层Object是音频引擎,之后其它所有的对象都需要音频引擎这个对象传入。

    它的Objects是有生命周期概念的。
    Objects ⼀般有三种状态,分别是:UNREALIZED (不可⽤),REALIZED(可⽤),SUSPENDED
    (挂起)。

    所有的Object在OpenSl里面我们拿到的都是一个SLObjectItf:

    SLObjectItf_

    //调用全局方法创建一个引擎对象(OpenSL ES唯一入口)

     SLresult result;
        result = slCreateEngine(&engineObject, 0, 0, 0, 0, 0);
    
    • 1
    • 2

    我们从官方文档里面看到了,其它都是传入对象,只有第一个engineObject是丢进去赋值的,这也是C语音赋值正常流程。
    这个engineObject的类型就是SLObjectItf,我们看看这个engineObject是什么东西。

    SL_API SLresult SLAPIENTRY slCreateEngine(
    	SLObjectItf             *pEngine,
    	SLuint32                numOptions,
    	const SLEngineOption    *pEngineOptions,
    	SLuint32                numInterfaces,
    	const SLInterfaceID     *pInterfaceIds,
    	const SLboolean         * pInterfaceRequired
    ) SL_API_DEPRECATED(30);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这是函数定义,具体实现在cpp文件那里,我们看下这个SLObjectItf的定义:

    typedef const struct SLObjectItf_ * const * SLObjectItf;
    
    • 1

    在C语言中,typedef关键字可以用来为已存在的类型定义一个新的名称。这里,typedef const struct SLObjectItf_ * const * SLObjectItf;是定义了一个新的类型名SLObjectItf,这个新类型是const struct SLObjectItf_ *类型的别名。

    这个语句可以分为两部分来解释:

    1、const struct SLObjectItf_ *:这是一个指向const struct SLObjectItf_类型的指针。struct SLObjectItf_是一个结构体类型,但是这里并没有给出这个结构体的具体定义,所以无法知道它包含哪些字段和数据。const关键字表示这个指针自身是一个常量,不能被修改,但指针所指向的内容是可以被修改的。

    2、* SLObjectItf:这是一个指向上面定义的const struct SLObjectItf_ *类型的指针。也就是说,SLObjectItf是一个指向指向const struct SLObjectItf_类型的指针的指针。这样的数据结构通常被用于实现动态链接库(DLL)或者共享库,因为这样的设计可以让使用者在使用这些库的时候不直接操作原始的接口,而是通过这个指针的指针来操作。

    简单来说:SLObjectItf是SLObjectItf_ 类型的别名,当调用调用全局方法创建一个引擎对象(唯一入口)的时候就会根据传入的参数类型来给这个SLObjectItf赋值。

    我们简单看下它的函数定义:

    struct SLObjectItf_ {
    	SLresult (*Realize) (
    		SLObjectItf self,
    		SLboolean async
    	);
    	SLresult (*Resume) (
    		SLObjectItf self,
    		SLboolean async
    	);
    	SLresult (*GetState) (
    		SLObjectItf self,
    		SLuint32 * pState
    	);
    	SLresult (*GetInterface) (
    		SLObjectItf self,
    		const SLInterfaceID iid,
    		void * pInterface
    	);
    	SLresult (*RegisterCallback) (
    		SLObjectItf self,
    		slObjectCallback callback,
    		void * pContext
    	);
    	void (*AbortAsyncOperation) (
    		SLObjectItf self
    	);
    	void (*Destroy) (
    		SLObjectItf self
    	);
    	SLresult (*SetPriority) (
    		SLObjectItf self,
    		SLint32 priority,
    		SLboolean preemptable
    	);
    	SLresult (*GetPriority) (
    		SLObjectItf self,
    		SLint32 *pPriority,
    		SLboolean *pPreemptable
    	);
    	SLresult (*SetLossOfControlInterfaces) (
    		SLObjectItf self,
    		SLint16 numInterfaces,
    		SLInterfaceID * pInterfaceIDs,
    		SLboolean enabled
    	);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    上面说的Objects和Interfaces的关系在这里就是:SLObjectItf_ 是对象,它定义了很多的函数,实例化这个对象之后我们就可以
    使用对象里面的函数,里面定义了接口(就是一个对象):Interfaces

    如果是java这种面对对象语音,真正的接口的话必须实例化才行,这里显然就是名义上的接口,既然人家说是按照面对对象的思维来设计,我们就按照这种思维来理解,但是不能把它代入到具体的语音规则中。

    创建引擎

    这是在播放PCM数据的demo中的代码:

    /***********  1 创建引擎 获取SLEngineItf***************/
        SLresult result;
        result = slCreateEngine(&engineObject, 0, 0, 0, 0, 0);
        if (result != SL_RESULT_SUCCESS)
            return;
        result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
        if (result != SL_RESULT_SUCCESS)
            return;
        result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
        if (result != SL_RESULT_SUCCESS)
            return;
        if (engineEngine) {
            LOGD("get SLEngineItf success");
        } else {
            LOGE("get SLEngineItf failed");
        }
        /***********         1 创建引擎       ***************/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这是在录音demo中的代码:

       SLEngineOption pEngineOptions[] = {(SLuint32) SL_ENGINEOPTION_THREADSAFE,
                                           (SLuint32) SL_BOOLEAN_TRUE};
        // 创建引擎对象,//调用全局方法创建一个引擎对象(OpenSL ES唯一入口)
        SLresult result;
        result = slCreateEngine(
                &engineObject, //对象地址,用于传出对象
                1, // 可选配置数组的大小
                pEngineOptions, // 选配置数组的参数 录音时候一般会这么配置,主要是为了兼容其它平台,避免出现不兼容情况。
                0,  //支持的接口数量
                nullptr, //具体的要支持的接口,是枚举的数组
                nullptr//具体的要支持的接口是开放的还是关闭的,也是一个数组,这三个参数长度是一致的
        );
        assert(SL_RESULT_SUCCESS == result);
        /* Realizing the SL Engine in synchronous mode. */
        //实例化这个对象
        result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
        assert(SL_RESULT_SUCCESS == result);
        // get the engine interface, which is needed in order to create other objects
        //从这个对象里面获取引擎接口
        (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
        assert(SL_RESULT_SUCCESS == result);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    创建过程要设计得这么麻烦?(object的生命周期)

    OpenSL ES 的 Object 一般有三种状态,分别是: UNREALIZED (不可用),REALIZED (可用),SUSPENDED(挂起) 。
    obiect 外干UNREALIZED(不用)状态时,系统不会为其分配资源: 调用 Realize 方法后便进入 REALIZED(可用)状态,此时对象的各个功能和资源可以正常访问;当系统音频相关的硬件设备被其他进程占用时,OpenSL ES Obiect 便会进入 SUSPENDED (挂起) 状态,随后调用 Resume 方法可使对象重回 REALIZED (可用)状态;当 0bject 使用结束后,调用 Destroy 方法释放资源,是对象重回 UNREALIZED (不可用)状态

    因为设计到硬件,所以它的使用有一个过程,不像是存代码创建赋值直接在内存中生成某个对象。

    这么多参数,参数类型这么多学习障碍太大?

    android端在NDK中使用OpenSL ES的创建过程非常麻烦,但实际上在开发过程这些代码是很固定的,基本没人会手打,都是复制张贴,所以无需担心那么多。
    比如创建混音器,参数选项那么多,实际上还真不太可能全研究透,而是什么需求配置什么参数,看具体场景需求之后再去查找,其它方面的大体都是固定式代码,所以有一定的了解即可。

    创建混音器

     /***********  2 创建混音器 ***************/
    
        const SLInterfaceID mids[1] = {SL_IID_ENVIRONMENTALREVERB};// 环境回响
        const SLboolean mreq[1] = {SL_BOOLEAN_FALSE};
        result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, mids, mreq);
        if (result != SL_RESULT_SUCCESS) {
            LOGE("CreateOutputMix failed");
            return;
        } else {
            LOGD("CreateOutputMix success");
        }
    
        //实例化混音器
        result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
        if (result != SL_RESULT_SUCCESS) {
            LOGE("mixer init failed");
        } else {
            LOGD("mixer init success");
        }
    
        result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB,
                                                  &outputMixEnvironmentalReverb);
        if (SL_RESULT_SUCCESS == result) {
            // 走廊效果
            SLEnvironmentalReverbSettings reverbSettings = SL_I3DL2_ENVIRONMENT_PRESET_STONECORRIDOR;
            result = (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(
                    outputMixEnvironmentalReverb, &reverbSettings);
            (void) result;
        }
    
        /***********  2 创建混音器 ***************/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    可以看到在这里object是我们创建引擎拿到的interface对象,通过它实例化我们的outputMixObject对象
    object和interface的说法其实我们根本不需要去管,一层嵌着一层,能大体知道它的设计思路即可,免得被它的对象和接口搞得自己混乱了。

    其它更具体的代码这里就不再细讲了,网上的各种代码例子非常多,这里其实是为了加深我自己学习过程中的疑惑问题。

  • 相关阅读:
    UDS诊断系列介绍02-传输层CANTP
    2022年世界大学声誉排名(完整版)
    Java多线程-线程创建的3种方式
    RT-DETR算法优化改进:Backbone改进 | EMO,结合 CNN 和 Transformer 的现代倒残差移动模块设计 | ICCV2023
    SpringCloud - 服务调用组件OpenFeign使用详解(二)
    金属压块液压打包机比例阀放大器
    UiPath:一家由生成式AI驱动的流程自动化软件公司
    论文阅读_大模型_ToolLLM
    Android 架构方面的一些拙见
    vivado 串行 I/O 硬件调试流程
  • 原文地址:https://blog.csdn.net/qq_34606099/article/details/131540249