Android OpenSL ES详解

作者: 孤独的根号十二 | 来源:发表于2019-03-01 16:12 被阅读13次

    简介

    NDK开发OpenSL ES跨平台高效音频解决方案.png

    OpenSL ES全称为Open Sound Library for Embedded Systems,即嵌入式音频加速标准。OpenSL ES是无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速 API。它为嵌入式移动多媒体设备上的本地 应用程序开发者提供了标准化、高性能、低响应时间的音频功能实现方法,同时还实现了软/硬件音频性能的直接跨平台部署,不仅降低了执行难度,而且促进了高级音频市场的发展。

    OpenSL ES里面有对象和接口的概念:
    对象:类似于C++中类用来提供一组资源及其状态的抽象,也就是我们可以根据特定类型type来获取一个音频录制的对象,但是对于这个对象我们并不能直接操作。

    接口:接口是对象提供一组特定功能方法的抽象,也就是可以从对象中获取接口,然后通过接口来改变对象的状态以便使用对象的功能。

    使用:

    1.导入OpenSL ES库

    因为opensl是内嵌在android系统里的,所以我们需要连接到我们工程使用

    #找打Android lib库里面的libOpenSLES.so的库
    find_library( OpenSLES-lib
                    OpenSLES )
    #链接到你的native工程的库
    target_link_libraries( your-native.so
                           ${OpenSLES-lib}
                         )
    

    然后引入头文件

    #include <SLES/OpenSLES.h>
    #include <SLES/OpenSLES_Android.h>
    

    2.创建OpenSL ES引擎并初始化

    SLObjectItf engineObject; //引擎对象
    SLEngineItf engineInterface; //引擎接口
    SLObjectItf outputMixObject; //混音器
    SLObjectItf audioPlayerObject; //播放器对象
    SLAndroidSimpleBufferQueueItf andioPlayerBufferQueueItf;    //缓冲器队列接口
    SLPlayItf audioPlayInterface;   //播放接口
    
    
    SLEngineOption options[] = {
            {(SLuint32)SL_ENGINEOPTION_THREADSAFE, (SLuint32)SL_BOOLEAN_TRUE}
        };
        slCreateEngine(&engineObject,ARRAY_LEN(engineObject),options,0,0,0);  //没有接口
    
    //实例化对象
    //象创建之后,处于未实例化状态,对象虽然存在但未分配任何资源,使用前先实例化(使用完之后destroy)
    RealizeObject(engineObject);
    

    关于slCreateEngine()这个全局方法:

    SL_API SLresult SLAPIENTRY slCreateEngine(
        SLObjectItf             *pEngine,           //对象地址,用于传出对象
        SLuint32                numOptions,         //配置参数数量
        const SLEngineOption    *pEngineOptions,    //配置参数,为枚举数组
        SLuint32                numInterfaces,      //支持的接口数量
        const SLInterfaceID     *pInterfaceIds,     //具体的要支持的接口,是枚举的数组
        const SLboolean         *pInterfaceRequired //具体的要支持的接口是开放的还是关闭的,也是一个数组,这三个参数长度是一致的
    );
    

    3.获取引擎接口

    (*engineObject)->GetInterface(engineObject,SL_IID_ENGINE,&engineInterface);
    

    4.实例化混音器

    //4.创建输出混音器
        (*engineInterface)->CreateOutputMix(engineInterface,&outputMixObject,0,0,0); //没有接口
    
        //实例化混音器
        RealizeObject(outputMixObject);
    

    5.创建音频播放对象

        // Android针对数据源的简单缓冲区队列定位器
        SLDataLocator_AndroidSimpleBufferQueue dataSourceLocator = {
            SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, // 定位器类型
            1                                        // 缓冲区数
        };
    
        // PCM数据源格式
        SLDataFormat_PCM dataSourceFormat = {
            SL_DATAFORMAT_PCM,        // 格式类型
            wav_get_channels(wav),    // 通道数
            wav_get_rate(wav) * 1000, // 毫赫兹/秒的样本数
            wav_get_bits(wav),        // 每个样本的位数
            wav_get_bits(wav),        // 容器大小
            SL_SPEAKER_FRONT_CENTER,  // 通道屏蔽
            SL_BYTEORDER_LITTLEENDIAN // 字节顺序
        };
    
        // 数据源是含有PCM格式的简单缓冲区队列
        SLDataSource dataSource = {
            &dataSourceLocator, // 数据定位器
            &dataSourceFormat   // 数据格式
        };
    
        // 针对数据接收器的输出混合定位器
        SLDataLocator_OutputMix dataSinkLocator = {
            SL_DATALOCATOR_OUTPUTMIX, // 定位器类型
            outputMixObject           // 输出混合
        };
    
        // 数据定位器是一个输出混合
        SLDataSink dataSink = {
            &dataSinkLocator, // 定位器
            0                 // 格式
        };
    
        // 需要的接口
        SLInterfaceID interfaceIds[] = {
            SL_IID_BUFFERQUEUE
        };
    
        // 需要的接口,如果所需要的接口不要用,请求将失败
        SLboolean requiredInterfaces[] = {
            SL_BOOLEAN_TRUE // for SL_IID_BUFFERQUEUE
        };
    (*engineInterface)->CreateAudioPlayer(
                engineInterface,
                &audioPlayerObject,
                &dataSource,
                &dataSink,
                ARRAY_LEN(interfaceIds),
                interfaceIds,
                requiredInterfaces);
    
    //实例化音频播放器
        RealizeObject(audioPlayerObject);
    

    6.获得缓冲区队列接口Buffer Queue Interface

    //通过缓冲区队列接口对缓冲区进行排序播放
        (*audioPlayerObject)->GetInterface(audioPlayerObject,SL_IID_BUFFERQUEUE,&andioPlayerBufferQueueItf);
    
    

    7.注册音频播放器回调函数

    当播放器完成对前一个缓冲区队列的播放时,回调函数会被调用,然后我们又继续读取音频数据,直到结束

    //缓冲区的大小
        bufferSize = wav_get_channels(wav) * wav_get_rate(wav) * wav_get_bits(wav);
        buffer = new unsigned char[bufferSize];
    PlayerContext *ctx = new PlayerContext(wav,buffer,bufferSize);
    (*andioPlayerBufferQueueItf)->RegisterCallback(andioPlayerBufferQueueItf,PlayerCallBack,ctx);
    

    RegisterCallback函数的原型:

        SLresult (*RegisterCallback) (
            SLAndroidSimpleBufferQueueItf self,
            slAndroidSimpleBufferQueueCallback callback,
            void* pContext
        );
    

    第一个参数是SLAndroidSimpleBufferQueueItf对象,第二个参数是回调函数,第三个参数是一个void,第三个参数一般指向一个封装的类或者结构体,保存一些回调函数中可能用到的信息,回调函数的规范是必须有两个参数,且第一个参数SLAndroidSimpleBufferQueueItf类型,第二个参数是void,指向封装的结构体
    PlayerContext:

    struct PlayerContext{
        WAV wav;
        unsigned char *buffer;
        size_t bufferSize;
    
        PlayerContext(WAV wav,
                unsigned char *buffer,
                size_t bufferSize){
            this->wav = wav;
            this->buffer = buffer;
            this->bufferSize = bufferSize;
        }
    };
    

    PlayerCallBack:

    void PlayerCallBack(SLAndroidSimpleBufferQueueItf andioPlayerBufferQueue,void *context){
        PlayerContext* ctx = (PlayerContext*)context;
        //读取数据
        ssize_t readSize = wav_read_data(ctx->wav,ctx->buffer,ctx->bufferSize);
        if(0 < readSize){
            (*andioPlayerBufferQueue)->Enqueue(andioPlayerBufferQueue,ctx->buffer,readSize);
        }else{
            //destroy context
            CloseWaveFile(ctx->wav); //关闭文件
            delete ctx->buffer; //释放缓存
        }
    }
    

    8.获取Play Interface

    通过对SetPlayState函数来启动播放音乐,一旦播放器被设置为播放状态,该音频播放器开始等待缓冲区排队就绪

        (*audioPlayerObject)->GetInterface(audioPlayerObject,SL_IID_PLAY,&audioPlayInterface);
        //设置播放状态
        (*audioPlayInterface)->SetPlayState(audioPlayInterface,SL_PLAYSTATE_PLAYING);
    

    9.开始

    第一个缓冲区入队

    PlayerCallBack(andioPlayerBufferQueueItf,ctx);
    

    相关文章

      网友评论

        本文标题:Android OpenSL ES详解

        本文链接:https://www.haomeiwen.com/subject/ikmduqtx.html