OpenSL ES - 嵌入式音频加速标准。OpenSL ES™ 是无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速API。它为嵌入式移动多媒体设备上的本地应用程序开发者提供标准化, 高性能,低响应时间的音频功能实现方法,并实现软/硬件音频性能的直接跨平台部署,降低执行难度,促进高级音频市场的发展。
OpenSL ES 提供了音频播放和音频采集的一套方案,但是并无法进行音频流的编解码,因此我们可以通过OpenSL ES来播放音频采样出来的原始pcm数据格式,和采样一段原始pcm数据出来,但是无法播放经过压缩的例如mp3的音频文件或流。(一些简单的格式貌似可以播放)
OpenSL ES提供的是基于C语言的API,但是它是基于对象和接口的方式提供的,会采用面向对象的思想开发API。OpenSL的对象是对一组资源及其状态的抽象(注意有点类似java里面的抽象类的概念),同时他们提供了一套方法来生成实现获取对应不同功能的接口,(有点类似多态的概念,但跟java的接口又完全不一样),个人理解这里的接口其实是指的上面抽象化的具体实例。这样说的比概念化,不是很轻易的理解,下面我就通过自己的理解用白水话解释一遍,有不同理解的不要喷哈,只要如何方便自己的理解都是对的。
OpenSL ES因为是C语言实现,因此没有类与对象的概念,但是语言木有不表示编程的思想木有,因此将音频处理所需要到的资源进行抽象化(例如涉及到引擎,混音器,播放器,我把它们定义为一组资源,他们的创建和实现等的过程都抽象化成为结构体SLObjectItf)。结构体SLObjectItf内部有几个函数指针,在使用的时候可以通过这些函数指针Realize来实现一组资源,可以通过函数指针GetState来获取这组资源的状态,或者通过函数指针RegisterCallback来对这组资源注册回调函数。在想要改变这组资源状态的时候,可以使用函数指针GetInterface通过传递不同的ID值来获取不同的接口来改变这组资源的不同属性。例如,我们可以通过CreateAudioPlayer创建一组来播放原始音频的资源,暂且命名为AudioPlayer:
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的一些概念的区分):
2、openSL步骤:
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};
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};
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};
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}; //输出指定为缓存方式(用过缓存队列回调获取采集出来的数据)
OpenSLDemo
1、OpenSL基类的封装:
下面代码抽取了OpenSL公共部分进行封装,并实现了引擎的创建和OpenSL启用的常规步骤
#include
#include
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