如何在项目中引入OpenSL ES
在android ndk相关目录下已经引入了opensles.so。
因此可以在 CMakeList.txt中加入 直接使用。
cmake_minimum_required(VERSION 3.4.1)
add_library(
native-lib
SHARED
native-lib.cpp)
target_link_libraries(
android
native-lib
log
OpenSLES)
Objects 和 Interfaces
OpenSL ES 有两个必须理解的概念,就是 Object (对象)和 Interface(接口) :
-
对象是一组功能的抽象,每个对象都包含了许多的接口,由这些接口提供具体的功能。
-
每个 Object 对象都提供了一些最基础的操作,比如:Realize,Resume,GetState,Destroy 等等,如果希望使用该对象支持的功能函数,则通过其 GetInterface 函数拿到 接口,然后通过接口来访问具体功能。
在 “OpenSLES.h” 文件可以看到 OpenSL ES 定义的所有 Object 对象的 ID,通过这些Object ID 来创建对应的对象实例。
/* Objects ID's */
#define SL_OBJECTID_ENGINE ((SLuint32) 0x00001001)
#define SL_OBJECTID_LEDDEVICE ((SLuint32) 0x00001002)
#define SL_OBJECTID_VIBRADEVICE ((SLuint32) 0x00001003)
#define SL_OBJECTID_AUDIOPLAYER ((SLuint32) 0x00001004)
#define SL_OBJECTID_AUDIORECORDER ((SLuint32) 0x00001005)
#define SL_OBJECTID_MIDIPLAYER ((SLuint32) 0x00001006)
#define SL_OBJECTID_LISTENER ((SLuint32) 0x00001007)
#define SL_OBJECTID_3DGROUP ((SLuint32) 0x00001008)
#define SL_OBJECTID_OUTPUTMIX ((SLuint32) 0x00001009)
#define SL_OBJECTID_METADATAEXTRACTOR ((SLuint32) 0x0000100A)
同样,“OpenSLES.h” 文件中还定义了所有的 Interface ID,通过 Interface ID 可以从对象中获取到对应的功能接口。
/* Standard Object Interface */
extern SL_API const SLInterfaceID SL_IID_OBJECT;
/** Output Mix Interface */
extern SL_API const SLInterfaceID SL_IID_OUTPUTMIX;
extern SL_API const SLInterfaceID SL_IID_PLAY;
/**Buffer Queue Interface*/
extern SL_API const SLInterfaceID SL_IID_BUFFERQUEUE;
extern SL_API const SLInterfaceID SL_IID_ENVIRONMENTALREVERB;
/** Engine Interface*/
extern SL_API const SLInterfaceID SL_IID_ENGINE;
//....只列出部分
OpenSL ES 的状态机制
任何一个 OpenSL ES 的对象,创建成功后,都进入 SL_OBJECT_STATE_UNREALIZED 状态,这种状态下,系统不会为它分配任何资源。
调用Realize 后的对象, 会分配相关的资源,进入 SL_OBJECT_STATE_REALIZED 状态,这是一种“可用”的状态,只有在这种状态下,对象的各个功能才能正常地访问。
当一些系统事件发生后,比如出现错误或者 Audio 设备被其他应用抢占,OpenSL ES 对象会进入 SL_OBJECT_STATE_SUSPENDED 状态, 调用Resume()可以恢复到 SL_OBJECT_STATE_REALIZED 状态。
当调用对象的 Destroy 函数后,则会释放资源,回到 SL_OBJECT_STATE_UNREALIZED 状态。
Engine Object
OpenSL ES 里面最核心的对象就是:Engine Object(音频引擎对象),它主要提供如下功能:
- 提供引擎接口: SLEngineItf,该接口可以用来创建所有其他的 Object 对象
Engine Objec创建 &初始化
SLObjectItf engineObject;
slCreateEngine(&engineObject,0, NULL, 0, NULL, NULL);
(*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
获取SLEngineItf接口,获取后 就可以使用 engineEngine 来创建 OpenSL ES 的其他对象了。
SLEngineItf engineEngine;
(*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &(engineEngine));
最后不用了就销毁
(*engineObject)->Destroy(engineObject);
使用流程
- 1、获取引擎接口
- 2、创建输出混音器
- 3、创建播放器,并获取播放接口
- 4、获取缓冲队列接口并给缓冲队列注册回调函数
- 5、设置播放状态、手动调用回调函数
1、获取引擎接口
slCreateEngine(&engineObject,0, NULL, 0, NULL, NULL);
(*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
(*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
获取引擎接口至关重要,后面所有的对象都是通过引擎接口创建的。
OpenSLES接口的获取都是遵循这三步,先创建对象、再初始化对象、最后通过接口id从对象获取对应功能的接口。最后不用的时候可以调用对象的Destroy函数进行销毁。
2、创建输出混音器
const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB};
const SLboolean req[1] = {SL_BOOLEAN_FALSE};
(*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids, req);
(*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
sLresult=(*outputMixObject)->GetInterface(outputMixObject,
SL_IID_ENVIRONMENTALREVERB,&outputMixEnvironmentalReverb);
if (sLresult==SL_RESULT_SUCCESS){
(*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(
outputMixEnvironmentalReverb, &reverbSettings);
(void)sLresult;
}
3、创建播放器并获取播放接口:创建播放器还需要为播放器指定Data Source 和 Data Sink 。
- data source 代表着输入源的信息,即数据从哪儿来、输入的数据格式是怎样的。
- data sink 代表着输出的信息,即数据输出到哪儿、以什么样的方式输出。
data source和data sink的结构体如下:
//data source
typedef struct SLDataSource_ {
void *pLocator;
void *pFormat;
} SLDataSource;
//data sink
typedef struct SLDataSink_ {
void *pLocator;
void *pFormat;
} SLDataSink;
对于pLocator来说,通常有下面这些常用值:
#define SL_DATALOCATOR_URI ((SLuint32) 0x00000001)//数据源是URI
#define SL_DATALOCATOR_ANDROIDFD ((SLuint32) 0x800007BC)//数据源通常是asset文件
#define SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE ((SLuint32) 0x800007BD)//数据源是缓冲队列
#define SL_DATALOCATOR_OUTPUTMIX ((SLuint32) 0x00000004)//输出到混音器
//....
//配置DataSource
SLDataLocator_AndroidSimpleBufferQueue loc_bufq={
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,//从缓存队列获取数据
2
};
//tips:数据源除了指定缓冲队列外,还有本地文件、URI
SLDataFormat_PCM format_pcm={
SL_DATAFORMAT_PCM,//是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,//播放结束的标志
};
SLDataSource dataSource={&loc_bufq,&format_pcm};
//配置DataSink,指定输出到输出混音器
SLDataLocator_OutputMix loc_outmix={SL_DATALOCATOR_OUTPUTMIX,outputMixObject};
SLDataSink dataSink={&loc_outmix,NULL};
//创建音频播放器对象
const SLInterfaceID iids[3] = {SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND, SL_IID_VOLUME};
const SLboolean ireq[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
(*engineEngine)->CreateAudioPlayer(engineEngine,&playerObject,
&dataSource,&dataSink,3,iids,ireq);
(*playerObject)->Realize(playerObject,SL_BOOLEAN_FALSE);
//获取播放接口
(*playerObject)->GetInterface(playerObject,SL_IID_PLAY,&player);
4、获取缓冲队列接口并给缓冲队列注册回调函数
(*playerObject)->GetInterface(playerObject,SL_IID_BUFFERQUEUE,&bufferQueueItf);
(*bufferQueueItf)->RegisterCallback(bufferQueueItf,bufferQueueCallable,NULL);
5、设置播放状态、手动调用回调函数
(*player)->SetPlayState(player,SL_PLAYSTATE_PLAYING);
bufferQueueCallable(bufferQueueItf,NULL);
当设置了播放器为播放状态,该音频播放器开始等待缓存取队列就绪。
回调函数如下:
void bufferQueueCallable(SLAndroidSimpleBufferQueueItf caller,void *pContext){
size_t len=getPcmData(&pcmData);
if(pcmData&&len!=-1){
(*bufferQueueItf)->Enqueue(bufferQueueItf,pcmData,len);
} else{
LOGD("读取结束");
if(file!=NULL){
fclose(file);
file=NULL;
}
}
}
第一次我们手动调用了回调函数,然后会通过Enqueue函数将数据入队,接着播放器会从缓冲队列中取出数据进行播放,每次缓冲队列数据播放完成都会调用回调函数,回调又接着往缓冲队列中放入数据。
最后在适当的时候释放所有资源:
if (playerObject!=NULL){
(*playerObject)->Destroy(playerObject);
playerObject=NULL;
bufferQueueItf=NULL;
}
if (outputMixObject!=NULL){
(*outputMixObject)->Destroy(outputMixObject);
outputMixObject=NULL;
outputMixEnvironmentalReverb=NULL;
}
if (engineObject!=NULL){
(*engineObject)->Destroy(engineObject);
engineObject=NULL;
engineEngine=NULL;
}
if(buff!=NULL){
free(buff);
buff=NULL;
}
源码地址:
github