Android OpenSL ES详解

简介

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);

推荐阅读更多精彩内容