OpenSL ES总结

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

OpenSL ES 提供了音频播放和音频采集的一套方案,但是并无法进行音频流的编解码,因此我们可以通过OpenSL ES来播放音频采样出来的原始pcm数据格式,和采样一段原始pcm数据出来,但是无法播放经过压缩的例如mp3的音频文件或流。(一些简单的格式貌似可以播放)

一、OpenSL ES的API:

OpenSL ES提供的是基于C语言的API,但是它是基于对象和接口的方式提供的,会采用面向对象的思想开发API。OpenSL的对象是对一组资源及其状态的抽象(注意有点类似java里面的抽象类的概念),同时他们提供了一套方法来生成实现获取对应不同功能的接口,(有点类似多态的概念,但跟java的接口又完全不一样),个人理解这里的接口其实是指的上面抽象化的具体实例。这样说的比概念化,不是很轻易的理解,下面我就通过自己的理解用白水话解释一遍,有不同理解的不要喷哈,只要如何方便自己的理解都是对的。

OpenSL ES因为是C语言实现,因此没有类与对象的概念,但是语言木有不表示编程的思想木有,因此将音频处理所需要到的资源进行抽象化(例如涉及到引擎,混音器,播放器,我把它们定义为一组资源,他们的创建和实现等的过程都抽象化成为结构体SLObjectItf)。结构体SLObjectItf内部有几个函数指针,在使用的时候可以通过这些函数指针Realize来实现一组资源,可以通过函数指针GetState来获取这组资源的状态,或者通过函数指针RegisterCallback来对这组资源注册回调函数。在想要改变这组资源状态的时候,可以使用函数指针GetInterface通过传递不同的ID值来获取不同的接口来改变这组资源的不同属性。例如,我们可以通过CreateAudioPlayer创建一组来播放原始音频的资源,暂且命名为AudioPlayer:

  • 可以在AudioPlayer这组资源中获取SLPlayItf接口并通过该接口来控制播放器播放或者暂停状态
  • 还可以从AudioPlayer这组资源中获取SLVolueItf接口来通过该接口来控制播放器的音量
  • 还可以从AudioPlayer这组资源中获取SLBufferQueueItf 接口并通过该接口为播放器填充队列的源数据
  • 可以从AudioPlay这组资源中获取SLEffectSendItf接口来实现音效发送的功能
  • 还可以AudioPlay这组资源中获取SLMuteSoloItf接口来实现声道是否关闭的功能

1、我们可以先看看对象接口(对某组资源的描述)的定义:

struct SLObjectItf_;
typedef const struct SLObjectItf_ * const * SLObjectItf;
struct SLObjectItf_ {
    //实现这组资源
	SLresult (*Realize) ( 
		SLObjectItf self,
		SLboolean async
	);
	SLresult (*Resume) (
		SLObjectItf self,
		SLboolean async
	);
	SLresult (*GetState) (
		SLObjectItf self,
		SLuint32 * pState
	);
    //根据不同的ID来获取这组资源具有的不同接口(注意区分java接口,可以理解成属性)
	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
	);
};

2、对象(资源)和接口的通用操作方式:

在使用OpenSL ES来进行原始音频数据的播放或采集,通常需要到下面接口(下文中所有的资源其实就是对opensl中定义的对象的别称,为了与java的一些概念的区分):

  • SLObjectItf :对象接口(个人理解成一组资源),可以通过函数*Create*来创建一组资源(对象接口),通过该对象接口调用函数指针Realize来对这组资源进行实现或者实例化,之后可以通过GetInterface函数获取这组资源的各种其他接口(来描述这组资源的不同属性),注意改函数获取出来的东西其实也是SLObjectItf 
  • SLEngineItf :引擎接口(opensl的万能资源),oensl所用到所有资源都需要通过调用SLEngineItf的create*函数来进行孵化,这就类似于树结构的根节点,使用slCreateEngine生成引擎对象接口(引擎资源),并通过它的GetInterface函数获取混音器对象接口和播放器对象接口,我们又可以通过播放器对象接口获取到音量控制对象接口,队列对象接口,音效对象接口等,注意区分java里面的接口
  • SLEnvironmentalReverbItf:环境回响音效接口(属于输出混音器资源),个人认为只是一种音效而已,从输出混音器资源对象接口中可以获取,在不需要这种音效的时候可以不用获取该接口来更改混音器资源里面的属性
  • SLPlayItf:播放器状态控制接口(从播放器或采样器资源里面获取),可以通过该接口来对播放器对象的状态进行控制,函数SetPlayState来对播放器的播放,暂停,停止进行控制
  • SLAndroidSimpleBufferQueueItf:播放器队列接口(从播放器资源或采集器里面获取),通过该接口为播放器对象这组资源进行数据队列回调的设置,以及开启队列的循环,提供了一种音频数据的缓存队列的方式
  • SLVolumeItf :播放器音量接口(从播放器资源中获取),通过该接口可以对播放器的输出音量进行控制,至于输入音量没有测试过

2、openSL步骤:

  • 创建并实现引擎:创建引擎是为了孵化其他接口
  • 创建并实现输出混音器:通过引擎孵化,输出混音器除了能够给增加音效,还实现了将声音输出特定硬件里面并发出声音
  • 设置数据输入:数据源(Data source)是媒体对象的输入参数(代表着输入源的信息,即数据从哪儿来、输入的数据参数是怎样的)。在作为播放器的时候,输入参数一般就是被播放的数据源,例如uri或者本地文件或者队列缓存
  • 设置数据输出:数据接收器(Data sink)是媒体对象的输出参数,即数据输出到哪儿、以什么样的参数来输出。在作为录音器的时候,输出参数一般就是采集出来的指定数据,例如采集出来的pcm数据
  • 创建播放器对象或采集器对象:通过引擎孵化,如果进行原始音频流的播放则孵化播放器对象资源,如果是采样声音则孵化采样器对象
  • 获取缓存队列接口并设置回调:通过播放器或者采集器的对象资源获取SLAndroidSimpleBufferQueueItf接口,并设置回调函数,在回调函数中可以进行音频数据流的读(读取本地文件送给播放器进行播放)或者写(从采集器获取数据并写如本地文件)
  • 获取播放器状态接口并设置播放状态:通过播放器或者采集器的对象资源获取SLPlayItf接口,播放器通过SetPlayState函数来设置播放状态,采集器通过SetRecordState设置采集状态
  • 通过缓存队列接口设置开启队列循环:调用Enqueue函数开始进行回调函数的调用,这个至关重要,回调函数不回调,就无法将数据送到播放器里面

3、输入输出:

数据源(Data source)是媒体对象的输入参数,指定媒体对象将从何处接收特定类型的数据(例如采样的音频或MIDI数据)

typedef struct SLDataSource_ {
      void *pLocator;
      void *pFormat;
} SLDataSource;

数据接收器(Data sink)是媒体对象的输入参数,指定媒体对象将发送特定类型数据的位置

typedef struct SLDataSink_ {
    void *pLocator;
    void *pFormat;
} SLDataSink;

OpenSL ES 里面,这两个结构体均是作为创建 Media Object 对象时的参数而存在的,Data source 代表着输入源的信息,即数据从哪儿来、输入的数据参数是怎样的;而 Data Sink 代表着输出的信息,即数据输出到哪儿、以什么样的参数来输出。其中SLDataSource和SLDataSink结构完全一样,pLocator表示流的位置(可以是本地文件,可以是URI,可以是缓存队列,还可以指定输出混音器),pFormat表示流的数据格式。下面我们可以看看OpenSL在不同场景下是如何配置输入输出源的呢:

  • 创建队列方式播放器:输入源设置为队列缓存模式并可以指定队列缓存大小,输出指定到了混响器(暂时理解成控制喇叭或者音响耳机的软件模块)
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, //数据源从队列缓存方式中获取
                                                   4};         //指定队列缓存大小
SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM,
                               1, 
                               SL_SAMPLINGRATE_8,
                               SL_PCMSAMPLEFORMAT_FIXED_16, 
                               SL_PCMSAMPLEFORMAT_FIXED_16,
                               SL_SPEAKER_FRONT_CENTER, 
                               SL_BYTEORDER_LITTLEENDIAN};
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, 
                                      outputMixObject};    //输出混音器
SLDataSink audioSnk = {&loc_outmix, NULL};
  • 创建URI播放器:输入源设置为URI模式并指定了uri地址,这里需要指定为SL_DATAFORMAT_MIME格式(只能对URI数据定位器使用MIME数据格式,而且只能对音频播放器使用。您不能将此数据格式用于音频录制器),输出同上
SLDataLocator_URI loc_uri = {SL_DATALOCATOR_URI, //指定输入源为URI方式
                            (SLchar *) utf8};    //uri字符串
SLDataFormat_MIME format_mime = {SL_DATAFORMAT_MIME, 
                                 NULL, 
                                 SL_CONTAINERTYPE_UNSPECIFIED};
SLDataSource audioSrc = {&loc_uri, &format_mime};
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, 
                                      outputMixObject};
SLDataSink audioSnk = {&loc_outmix, NULL};
  • 创建文件流播放器:输入源设置为文件模式,输入源的数据格式同上,个人理解这样指定实现了OpenSL根据输入源URI或者文件的来进行自适应的数据格式
SLDataLocator_AndroidFD loc_fd = {SL_DATALOCATOR_ANDROIDFD, //输入流为文件
                                  fd,                       //文件描述符
                                  start,                    //输入流的起始位置
                                  length};      //输入流的长度 从改文件中获取指定位置指定长度
SLDataFormat_MIME format_mime = {SL_DATAFORMAT_MIME, 
                                 NULL, 
                                 SL_CONTAINERTYPE_UNSPECIFIED};
SLDataSource audioSrc = {&loc_fd, &format_mime};
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
SLDataSink audioSnk = {&loc_outmix, NULL};
  • 创建录音器:输入源设置为SL_DATALOCATOR_IODEVICE,个人理解这里的配置是将系统的默认音频输入的IO设备作为OpenSL输入源,经过OpenSL的处理转换成数字信号,并放在缓存队列里面,我们可以通过设置缓存队列的方式将这些数据提取出来
SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, 
                                  SL_IODEVICE_AUDIOINPUT,
                                  SL_DEFAULTDEVICEID_AUDIOINPUT, 
                                  NULL};
SLDataSource audioSrc = {&loc_dev, NULL};
SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 
                                                 4};
SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, 
                               1, 
                               SL_SAMPLINGRATE_16,
                               SL_PCMSAMPLEFORMAT_FIXED_16, 
                               SL_PCMSAMPLEFORMAT_FIXED_16,
                               SL_SPEAKER_FRONT_CENTER, 
                               SL_BYTEORDER_LITTLEENDIAN};
SLDataSink audioSnk = {&loc_bq, &format_pcm};    //输出指定为缓存方式(用过缓存队列回调获取采集出来的数据)

二、OpenSL示例:

OpenSLDemo

1、OpenSL基类的封装:

下面代码抽取了OpenSL公共部分进行封装,并实现了引擎的创建和OpenSL启用的常规步骤

#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
class SLBase{
public:
    SLBase();
    virtual ~SLBase();
    void Start();
    void Stop();
protected:
    //引擎
    SLObjectItf engineObject = nullptr;
    SLEngineItf engineEngine = nullptr;
    bool CreateEngine();                    //创建引擎
    void ReleaseEngine();                   //释放引擎
    //输入输出
    SLDataSource dataSource;
    SLDataSink dataSink;
    virtual bool SetDataSource()=0;         //设置输入源
    virtual bool SetDataSink()=0;           //设置输出源
    //播放器或者录音器
    virtual bool CreateFeature()=0;         //创建XXX器
    virtual void ReleaseFeature()=0;        //释放XXX器
    virtual bool SetStateRuning()=0;        //设置XXX器运行状态
    virtual bool SetStateStoping()=0;       //设置XXX器停止状态
    //队列
    bool isQueueLooping = false;
    SLAndroidSimpleBufferQueueItf queueItf;
    slAndroidSimpleBufferQueueCallback queueCallBack;
    void SetQueueState(bool isLoop);
public:
    void SetQueueCallBack(slAndroidSimpleBufferQueueCallback callback);
    bool SendQueueLoop(const void *pBuffer,SLuint32 size);
    bool IsQueueLooping() const { return isQueueLooping; };
    bool IsQueueSelf(SLAndroidSimpleBufferQueueItf queue);
};
SLBase::SLBase() {}
SLBase::~SLBase() {
    ReleaseEngine();
}
//设置回调方法
void SLBase::SetQueueCallBack(slAndroidSimpleBufferQueueCallback callback) {
    queueCallBack = callback;
}
/**
 * 设置队列状态
 * @param isLoop true正在轮询状态
 */
void SLBase::SetQueueState(bool isLoop) {
    isQueueLooping = isLoop;
}
// 像队列发送数据并轮询
bool SLBase::SendQueueLoop(const void *pBuffer, SLuint32 size) {
    if(!isQueueLooping)return false;
    SLresult result = (*queueItf)->Enqueue(queueItf, pBuffer,size);
    if(result!=SL_RESULT_SUCCESS) return false;
    
    return true;
}
/**
 * 检查回调函数回来的队列是否是自己
 * @param queue 因为SLAndroidSimpleBufferQueueItf已经实现了==运算符重载,因此不需要来比较是否为同一个对象(即不需要比较他们的地址是否相等,已经验证他们的地址不相等)
 * @return
 */
bool SLBase::IsQueueSelf(SLAndroidSimpleBufferQueueItf queue) {
    if(queue == queueItf)
        return true;
    else
        return false;
}
bool SLBase::CreateEngine() {
    SLresult result;
    result = slCreateEngine(&engineObject, 0, nullptr, 0, nullptr, nullptr);
    if(result != SL_RESULT_SUCCESS)return false;
    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    if(result != SL_RESULT_SUCCESS)return false;
    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
    if(result != SL_RESULT_SUCCESS) return false;
    return true;
}
void SLBase::ReleaseEngine() {
    engineEngine = nullptr;
    if (engineObject != nullptr) {
        (*engineObject)->Destroy(engineObject);
        engineObject = nullptr;
    }
}
void SLBase::Start() {
    //创建引擎
    bool isSucced = CreateEngine();
    if(!isSucced)return;
    //设置输入源
    isSucced = SetDataSource();
    if(!isSucced)return;
    //设置输出源
    isSucced = SetDataSink();
    if(!isSucced)return;
    //创建播放器或者录音器
    isSucced = CreateFeature();
    if(!isSucced)return;
    //设置运行状态
    isSucced = SetStateRuning();
    if(!isSucced)return;
    //开启轮询 注意:最后需要手动调用SendQueueLoop方法来开启轮询
    SetQueueState(true);
}
void SLBase::Stop() {
    LOGE("SLBase-Stop");
    //停止轮询
    SetQueueState(false);
    LOGE("SLBase-停止轮询");
    SetStateStoping();
    LOGE("SLBase-SetStateStoping");
    ReleaseFeature();
    LOGE("SLBase-ReleaseFeature");
    ReleaseEngine();
    LOGE("SLBase-ReleaseEngine");
}

2、OpenSL录音器实现:

下面代码封装了录音器并继承SLBase,主要封装了录音器的创建过程以及录音器的输入输出设置

class SLRecorder : public SLBase{
public:
    SLRecorder();
    virtual ~SLRecorder();
protected:
    SLObjectItf recorderObject;
    SLRecordItf recorderItf;
    virtual bool SetDataSource();           //设置输入源
    virtual bool SetDataSink();             //设置输出源
    virtual bool CreateFeature();           //创建XXX器
    virtual void ReleaseFeature();          //释放XXX器
    virtual bool SetStateRuning();          //设置XXX器运行状态
    virtual bool SetStateStoping();         //设置XXX器停止状态
};
SLRecorder::SLRecorder() : SLBase(){}
SLRecorder::~SLRecorder() {
    ReleaseFeature();
}
bool SLRecorder::SetDataSource() {
    static SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE,
                                             SL_IODEVICE_AUDIOINPUT,
                                             SL_DEFAULTDEVICEID_AUDIOINPUT,
                                             nullptr};
    //注意loc_dev不用static修饰在该函数结束就会被释放
    dataSource = {&loc_dev, nullptr};
    return true;
}
bool SLRecorder::SetDataSink() {
    static SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 10};
    static SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM,
                                          2,
                                          SL_SAMPLINGRATE_44_1,
                                          SL_PCMSAMPLEFORMAT_FIXED_16,
                                          SL_PCMSAMPLEFORMAT_FIXED_16,
                                          SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT,//声道
                                          SL_BYTEORDER_LITTLEENDIAN};
    dataSink = {&loc_bq, &format_pcm};
    return true;
}
//创建录音器
bool SLRecorder::CreateFeature() {
    SLresult result;
    const int length = 1;
    const SLInterfaceID ids[length] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
    const SLboolean req[length] = {SL_BOOLEAN_TRUE};
    result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &dataSource,&dataSink, length, ids, req);
    if(SL_RESULT_SUCCESS != result)return false;
    result = (*recorderObject)->Realize(recorderObject,SL_BOOLEAN_FALSE);
    if(SL_RESULT_SUCCESS != result)return false;
    result = (*recorderObject)->GetInterface(recorderObject,SL_IID_RECORD,&recorderItf);
    if(SL_RESULT_SUCCESS != result)return false;
    result = (*recorderObject)->GetInterface(recorderObject,SL_IID_ANDROIDSIMPLEBUFFERQUEUE,&queueItf);
    if(SL_RESULT_SUCCESS != result) return false;
    result = (*queueItf)->RegisterCallback(queueItf,queueCallBack, nullptr);
    if(SL_RESULT_SUCCESS != result)return false;
    return true;
}
//释放录音器
void SLRecorder::ReleaseFeature() {
    if (recorderObject != nullptr) {
        (*recorderObject)->Destroy(recorderObject);
        recorderObject = nullptr;
        queueItf = nullptr;
    }
}
bool SLRecorder::SetStateRuning() {
    SLresult result = (*recorderItf)->SetRecordState(recorderItf,SL_RECORDSTATE_RECORDING);
    if(result!=SL_RESULT_SUCCESS)return false;
    return true;
}
bool SLRecorder::SetStateStoping() {
    SLresult result = (*recorderItf)->SetRecordState(recorderItf,SL_RECORDSTATE_STOPPED);
    if(result!=SL_RESULT_SUCCESS)return false;
    return true;
}

3、OpenSL播放器实现:

下面代码封装了播放并继承SLBase,主要封装了播放的创建过程以及播放器的输入输出设置

class SLPlayer:public SLBase{
public:
    SLPlayer();
    virtual ~SLPlayer();
protected:
    //输出混音器
    SLObjectItf outMixObject;
    //播放器
    SLObjectItf playerObject;
    SLPlayItf playerItf;
    virtual bool SetDataSource();           //设置输入源
    virtual bool SetDataSink();             //设置输出源
    virtual bool CreateFeature();           //创建XXX器
    virtual void ReleaseFeature();          //释放XXX器
    virtual bool SetStateRuning();          //设置XXX器运行状态
    virtual bool SetStateStoping();         //设置XXX器停止状态
};
SLPlayer::SLPlayer() : SLBase(){}
SLPlayer::~SLPlayer() {
    ReleaseFeature();
}
bool SLPlayer::SetDataSource() {
    static SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 4};
    static SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM,
                                          2,
                                          SL_SAMPLINGRATE_44_1,
                                          SL_PCMSAMPLEFORMAT_FIXED_16,
                                          SL_PCMSAMPLEFORMAT_FIXED_16,
                                          SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT,//声道
                                          SL_BYTEORDER_LITTLEENDIAN};
    //注意loc_bufq和format_pcm不用static修饰在该函数结束就会被释放
    dataSource = {&loc_bufq, &format_pcm};
    return true;
}
bool SLPlayer::SetDataSink() {
    SLresult result;
    const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB};
    const SLboolean req[1] = {SL_BOOLEAN_FALSE};
    result = (*engineEngine)->CreateOutputMix(engineEngine, &outMixObject, 1, ids, req);
    if(SL_RESULT_SUCCESS != result) return false;
    result = (*outMixObject)->Realize(outMixObject, SL_BOOLEAN_FALSE);
    if(SL_RESULT_SUCCESS != result) return false;
    static SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outMixObject};
    //注意loc_outmix不用static修饰在该函数结束就会被释放
    dataSink = {&loc_outmix, nullptr};
    return true;
}
//创建缓存播放器
bool SLPlayer::CreateFeature() {
    SLresult result;
    const int length = 2;
    const SLInterfaceID ids[length] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
    const SLboolean req[length] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
    result = (*engineEngine)->CreateAudioPlayer(engineEngine, &playerObject, &dataSource, &dataSink,length, ids, req);
    if (SL_RESULT_SUCCESS != result)return false;
    result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
    if (SL_RESULT_SUCCESS != result) return false;
    result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerItf);
    if (SL_RESULT_SUCCESS != result) return false;
    result = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &queueItf);
    if (SL_RESULT_SUCCESS != result) return false;
    if (queueCallBack == nullptr)return true;
    result = (*queueItf)->RegisterCallback(queueItf, queueCallBack, nullptr);
    if (SL_RESULT_SUCCESS != result)return false;
    return true;
}
void SLPlayer::ReleaseFeature() {
    if(playerObject != nullptr) {
        (*playerObject)->Destroy(playerObject);
        playerObject = nullptr;
        playerItf = nullptr;
        queueItf = nullptr;
    }
    if(outMixObject != nullptr){
        (*outMixObject)->Destroy(outMixObject);
        outMixObject = nullptr;
    }
}
bool SLPlayer::SetStateRuning() {
    SLresult result = (*playerItf)->SetPlayState(playerItf,SL_PLAYSTATE_PLAYING);
    if(result!=SL_RESULT_SUCCESS) return false;
    return true;
}
bool SLPlayer::SetStateStoping() {
    SLresult result = (*playerItf)->SetPlayState(playerItf,SL_PLAYSTATE_STOPPED);
    if(result!=SL_RESULT_SUCCESS)return false;
    return true;
}

4、JNI调用OpenSL的封装:

下面代码是JNI通过调用上面封装的类,通过基类SLBase指针多态的方式操作OpenSL的播放器和录音器功能,并将回调函数分离出来外界进行操作,除此之外进行采样和播放的文件处理

#define BUF_SIZE (1024*4)
#define DEF_FILE_PATH "/storage/emulated/0/SHEN_RES/aqjz.pcm"
#define TEMP_FILE_PATH "/storage/emulated/0/SHEN_RES/temp.pcm"
FILE *readFp = nullptr;     //播放器用到的读取文件
FILE *writeFp = nullptr;    //录音器用到的写入文件
char *readBuf = nullptr;    //读取缓存
char *writeBuf = nullptr;   //写入缓存
SLBase *player;             //播放器
SLBase *recorder;           //录音器
void initPlayerResource(){
    if(readFp == nullptr) readFp = fopen(TEMP_FILE_PATH, "rb"); //华为
    if(readBuf == nullptr) readBuf = new char[BUF_SIZE];
}
void initRecorderResource(){
    if(!writeFp) writeFp = fopen(TEMP_FILE_PATH, "wb+"); //华为
    if(writeBuf == nullptr) writeBuf = new char[BUF_SIZE];
}
void freePlayerResource(){
    if(readFp != nullptr) {
        fclose(readFp);
        readFp = nullptr;
    }
    if(readBuf != nullptr){
        delete readBuf;
        readBuf = nullptr;
    }
}
void freeRecorderResource(){
    if(writeFp != nullptr) {
        fclose(writeFp);
        writeFp = nullptr;
    }
    if(writeBuf != nullptr){
        delete writeBuf;
        writeBuf = nullptr;
    }
}
//播放器的回调函数
void PlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {
    if(readFp == nullptr) return;
    if (player == nullptr) return;
    if(!player->IsQueueSelf(bq)) return;
    if(player->IsQueueLooping()){
        int len = fread(readBuf,1,BUF_SIZE,readFp);
        LOGD("PlayerCallback:len=%d",len);
        if (len > 0) player->SendQueueLoop(readBuf,len);
    }
}
//录音器的回调函数
void RecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {
    if(!writeFp)return;
    if (recorder == nullptr)return;
    if(!recorder->IsQueueSelf(bq)) return;
    if(recorder->IsQueueLooping()){
        fwrite(writeBuf,1,BUF_SIZE,writeFp);
        fflush(writeFp);
        recorder->SendQueueLoop(writeBuf,BUF_SIZE);
    }
}
extern "C" JNIEXPORT void JNICALL Java_com_shen_opensles_MainActivity_startPlay(JNIEnv *env, jobject instance) {
    initPlayerResource();
    player = new SLPlayer();
    player->SetQueueCallBack(PlayerCallback);
    player->Start();
    player->SendQueueLoop("",1);    //开启轮询
}
extern "C" JNIEXPORT void JNICALL Java_com_shen_opensles_MainActivity_stopPlay(JNIEnv *env, jobject instance) {
    player->Stop();
    freePlayerResource();
    delete player;
}
extern "C" JNIEXPORT void JNICALL Java_com_shen_opensles_MainActivity_startRecord(JNIEnv *env, jobject instance) {
    initRecorderResource();
    recorder = new SLRecorder();
    recorder->SetQueueCallBack(RecorderCallback);
    recorder->Start();
    recorder->SendQueueLoop(writeBuf,BUF_SIZE); //因为开启轮询的方式依赖外部缓存,因此提供了一个函数让外部调用
}
extern "C" JNIEXPORT void JNICALL Java_com_shen_opensles_MainActivity_stopRecord(JNIEnv *env, jobject instance) {
    recorder->Stop();
    freeRecorderResource();
    delete recorder;
}

5、OpenSL的音量控制:

//获取出来的最大音量Level为0
result = (*bqPlayerVolume)->GetMaxVolumeLevel(bqPlayerVolume,&vol);
//根据官方示例的音量计算公式
vol = (100-volue) * (-50);
//设置音量,范围为负数到0
result = (*bqPlayerVolume)->SetVolumeLevel(bqPlayerVolume,vol);
//经过测试上面公式vol=100,SetLevel方法参数为0,这个时候音量最大
//Vol=0,SetLevel方法参数为-5000,这个时候还是有音量,但不知道是不是最小

6、两种buffer队列ID:

在写上面demo的时候发现,在录音器获取队列接口的时候总是失败,后面对比官方示例发现,从播放器录音器中获取队列接口的时候,传的SL_ID不一样如下:

//从录音器中获取队列接口
(*recordObject)->GetInterface(recordObject,SL_IID_ANDROIDSIMPLEBUFFERQUEUE,&queueItf);
//从播放器中获取队列接口
(*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &queueItf);

//OpenSLES.h定义SL_IID_BUFFERQUEUE
extern SL_API const SLInterfaceID SL_IID_BUFFERQUEUE;
//OpenSLES_Android.h定义SL_IID_ANDROIDSIMPLEBUFFERQUEUE
extern SL_API const SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
//xref: /frameworks/wilhelm/src/sl_iid.cpp定义如下
const SLInterfaceID SL_IID_BUFFERQUEUE = &SL_IID_array[MPH_BUFFERQUEUE];
const SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE = &SL_IID_array[MPH_ANDROIDSIMPLEBUFFERQUEUE];
继续跟踪源码发现在xref: /frameworks/wilhelm/src/OpenSLES_IID.cpp文件中定义了数组SL_IID_array,关于他们的定义如下:

从上面发现,他们的值不一样,估计跟安卓底层有关系吧,官方也没有给出具体的解释,暂且作罢

参考链接:

音频数字信号详解

android平台OpenSL ES播放PCM数据

Android的声音编程--使用OpenSL ES Audio

 

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Table of Contents PART 1: USER MANUAL ......................................................................................... 1 1 OVERVIEW ..................................................................................................... 2 2 FEATURES AND PROFILES .............................................................................. 9 3 DESIGN OVERVIEW ...................................................................................... 14 FUNCTIONAL OVERVIEW .............................................................................. 30 PART 2: API REFERENCE ..................................................................................... 60 5 BASE TYPES AND UNITS ............................................................................... 61 6 FUNCTIONS .................................................................................................. 64 7 OBJECT DEFINITIONS .................................................................................. 67 8 INTERFACE DEFINITIONS ............................................................................ 93 9 MACROS AND TYPEDEFS............................................................................. 439 PART 3: APPENDICES ........................................................................................ 501 APPENDIX A: REFERENCES ............................................................................... 502 APPENDIX B: SAMPLE CODE.............................................................................. 504 APPENDIX C: USE CASE SAMPLE CODE .............................................................. 555 APPENDIX D: OBJECT-INTERFACE MAPPING..................................................... 585

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

诸神黄昏EX

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

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

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

打赏作者

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

抵扣说明:

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

余额充值