目录
- OpenSL ES是什么?
- 主要功能
- Android 平台的OpenSL ES
- 使用OpenSL ES 的优点
- API简要介绍
- 示例
- 参考
1. OpenSL ES是什么?
OpenSL ES(Open Sound Library for Embedded Systems,开源的嵌入式声音库)是一个免授权费、跨平台、C语言编写的适用于嵌入式系统的硬件加速音频库。它为移动和游戏行业的开发者提供标准化、高性能、低延迟的方法来实现音频功能,并致力于跨多个平台轻松移植应用程序。OpenSL ES由非营利性技术联盟Khronos Group管理。
OpenSL ES的设计目标是让应用程序开发人员能够访问高级音频功能,如3D定位音频和MIDI播放,同时努力在制造商和平台之间轻松实现应用程序移植。
简要来说 OpenSL ES 是一套针对嵌入式平台的音频功能API标准。
2. 主要功能
OpenSL ES主要功能包括:
- 基本音频播放和录制。
- 3D音频效果,包括3D定位音频。
- 音乐体验增强效果,包括低音增强和环境混响。
- 缓冲队列。
3. Android 平台的OpenSL ES
Android 2.3将OpenSL ES 1.0.1作为其NDK的一部分。在之后的版本中,实现的延迟有所改进。
Android 实现的 OpenSL ES 只是 OpenSL ES 1.0.1 的子集,并且进行了扩展。因此,对于 OpenSL ES API 的使用,我们需要特别留意哪些是 Android 支持的,哪些是不支持的。
不支持的功能:
- 不支持 MIDI。
- 不支持直接播放 DRM 或者 加密的内容。
- 不支持音频数据的编解码,如需编解码,需要使用 MediaCodec API 或者第三方库。
- 在音频延时方面,相比于JAVA的 API,并没有特别明显地改进。
4. 使用OpenSL ES 的优点
- 相比于 Java API,避免音频数据频繁在 native 层和 Java 层拷贝,提高效率。
- 相比于 Java API,可以更灵活地控制参数。
- 使用 C 代码,可以做深度优化,比如采用 NEON 优化。
5. API简要介绍
OpenSL ES 虽然是 C 语言编写,但是它的接口采用的是面向对象的方式,并不是提供一系列的函数接口,而是以 Interface 的方式来提供 API,这是理解 OpenSL ES API 的一个比较重要的点。
它的大都数 API 需要这样访问:
//下面代码是对 Audio Engine 对象进行 “初始化”
SLEngineItf engineObject;
SLresult result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
如果在 Android NDK 下开发过 C 代码,就应该不会太陌生,因为我们调用 “JNI* env” 的函数也是这个样子去调用的。
5.1 Object 和 Interface
OpenSL ES 有两个重要的概念 Object 和 Interface,“对象”和“接口”。
(1) 每个 Object 可能会存在一个或者多个 Interface,官方为每一种 Object 都定义了一系列的 Interface。
(2) 每个 Object 对象都提供了一些最基础的操作,比如:Realize,Resume,GetState,Destroy 等等,如果希望使用该对象支持的功能函数,则必须通过其 GetInterface 函数拿到 Interface 接口,然后通过 Interface 来访问功能函数。
(3) 并不是每个系统上都实现了 OpenSL ES 为 Object 定义的所有 Interface,所以在获取 Interface 的时候需要做一些选择和判断。
5.2 OpenSL ES的状态机制
OpenSL ES 有一个重要的概念:状态机制。如图所示:
任何一个 OpenSL ES 的对象,创建成功后,都进入 SL_OBJECT_STATE_UNREALIZED 状态,这种状态下,系统不会为它分配任何资源。
Realize 后的对象,就会进入 SL_OBJECT_STATE_REALIZED 状态,这是一种“可用”的状态,只有在这种状态下,对象的各个功能和资源才能正常地访问。
当一些系统事件发生后,比如出现错误或者 Audio 设备被其他应用抢占,OpenSL ES 对象会进入 SL_OBJECT_STATE_SUSPENDED 状态,如果希望恢复正常使用,需要调用 Resume 函数。
当调用对象的 Destroy 函数后,则会释放资源,并回到 SL_OBJECT_STATE_UNREALIZED 状态。
Engine对象是OpenSL ES API的入口点,这个对象使你能够创建OpenSL ES中所有其他对象。
Engine对象由全局的对象slCreateEngine()创建得到,创建的结果是Engine对象的一个SLObjectItf的接口。
5.3 Engine Object 和 SLEngineItf Interface
Engine Object是OpenSL ES 里面最核心的对象,
它主要提供如下两个功能:
(1) 管理 Audio Engine 的生命周期。
(2) 提供管理接口: SLEngineItf,该接口可以用来创建所有其他的 Object 对象。
(3) 提供设备属性查询接口:SLEngineCapabilitiesItf 和 SLAudioIODeviceCapabilitiesItf,这些接口可以查询设备的一些属性信息。
Engine Object 对象的创建和销毁的方法如下:
//创建
SLObjectItf engineObject;
slCreateEngine( &engineObject, 0, nullptr, 0, nullptr, nullptr );
//初始化
(*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
//销毁
(*engineObject)->Destroy(engineObject);
slCreateEngine的函数定义如下:
SLresult SLAPIENTRY slCreateEngine(
SLObjectItf *pEngine,
SLuint32 numOptions
constSLEngineOption *pEngineOptions,
SLuint32 numInterfaces,
constSLInterfaceID *pInterfaceIds,
constSLboolean *pInterfaceRequired
)
参数说明如下:
pEngine:指向输出的engine对象的指针。
numOptions:可选配置数组的大小。
pEngineOptions:可选配置数组。
numInterfaces:对象要求支持的接口数目,不包含隐含的接口。
pInterfaceId:对象需要支持的接口id的数组。
pInterfaceRequired:指定每个要求接口的接口是可选或者必须的标志位数组。如果要求的接口没有实现,创建对象会失败并返回错误码
SL_RESULT_FEATURE_UNSUPPORTED。
获取管理接口:
SLEngineItf engineEngine;
(*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &(engineEngine));
下面就可以使用 engineEngine 来创建所有 OpenSL ES 的其他对象了。
5.4 Media Object
Media Object代表着多媒体处理功能的抽象,如呈现和捕获媒体流的对象player、recorder 等等。
可以通过 SLEngineItf 提供的 CreateAudioPlayer 方法来创建一个 player 对象实例,可以通过 SLEngineItf 提供的 CreateAudioRecorder 方法来创建一个 recorder 实例。
5.5 Data Source 和 Data Sink
数据源(Data source)是媒体对象的输入参数,指定媒体对象将从何处接收特定类型的数据(例如采样的音频或MIDI数据)。 数据接收器(Data sink)是媒体对象的输入参数,指定媒体对象将发送特定类型数据的位置。
OpenSL ES 里面,这两个结构体均是作为创建 Media Object 对象时的参数而存在的,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 主要有如下几种:
SLDataLocator_Address
SLDataLocator_BufferQueue
SLDataLocator_IODevice
SLDataLocator_MIDIBufferQueue
SLDataLocator_URI
也就是说,Media Object 对象的输入源/输出源,既可以是 URL,也可以 Device,或者来自于缓冲区队列等等,完全是由 Media Object 对象的具体类型和应用场景来配置。
数据格式(data format)标识数据流的特征,包括以下几种类型:
- 基于MIME类型的格式
- PCM格式
5.6 Metadata Extractor Object
播放器对象支持读取媒体内容的元数据。但是有时候只是读取元数据而不播放媒体内容是很有用处的。
Metadata Extractor Object可以用于读取元数据而不需要分配用于媒体播放的资源。
5.7 示例说明
(1) 音频播放场景:
使用了Audio Player对象来实现播放音频功能。使用engine对象的 SLEngineItf接口来创建Audio Player,创建之后与Output mix相关联用于音频输出。输入以URI作为示例,Output Mix默认与系统相关的默认输出设备关联。
(2) 录制音频场景:
通过Audio Recorder对象来实现音频录制功能。
6. 示例
OpenSL ES播放PCM数据主要有如下7个步骤:
1.创建EngineObject
2.设置DataSource
3.设置DataSink
4.创建播放器
5.设置缓冲队列和回调函数
6.设置播放状态
7.启动回调函数
6.1 创建接口对象
SLresult OpenGLESPlayer::createEngine() {
LOGD("createEngine()");
SLresult result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
if(result != SL_RESULT_SUCCESS) {
LOGD("slCreateEngine failed, result=%d", result);
return result;
}
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
if(result != SL_RESULT_SUCCESS) {
LOGD("engineObject Realize failed, result=%d", result);
return result;
}
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &(engineEngine));
if(result != SL_RESULT_SUCCESS) {
LOGD("engineObject GetInterface failed, result=%d", result);
return result;
}
return result;
}
6.2 设置混音器
// set DataSource
SLDataLocator_AndroidSimpleBufferQueue android_queue={SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,2};
SLDataFormat_PCM sLDataFormat_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
};
SLDataSource slDataSource = {&android_queue, &sLDataFormat_pcm};
6.3 设置DataSink
//set DataSink
const SLInterfaceID mids[1] = {SL_IID_ENVIRONMENTALREVERB};
const SLboolean mreq[1] = {SL_BOOLEAN_FALSE};
ret = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, mids, mreq);
if(ret != SL_RESULT_SUCCESS) {
LOGD("CreateOutputMix failed, ret=%d", ret);
return ret;
}
ret = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
if(ret != SL_RESULT_SUCCESS) {
LOGD("Realize failed, result=%d", ret);
return ret;
}
SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
SLDataSink audioSnk = {&outputMix, NULL};
6.4 创建播放器
const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};
const SLboolean req[1] = {SL_BOOLEAN_TRUE};
ret = (*engineEngine)->CreateAudioPlayer(engineEngine, &playerObject, &slDataSource, &audioSnk, 1, ids, req);
if (ret != SL_RESULT_SUCCESS) {
LOGD("CreateAudioPlayer() failed.");
return ret;
}
ret = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
if (ret != SL_RESULT_SUCCESS) {
LOGD("playerObject Realize() failed.");
return ret;
}
ret = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay);
if (ret != SL_RESULT_SUCCESS) {
LOGD("playerObject GetInterface(SL_IID_PLAY) failed.");
return ret;
}
6.5 设置缓冲队列和回调函数
ret = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &simpleBufferQueue);
if (ret != SL_RESULT_SUCCESS) {
LOGD("playerObject GetInterface(SL_IID_BUFFERQUEUE) failed.");
return ret;
}
ret = (*simpleBufferQueue)->RegisterCallback(simpleBufferQueue, pcmBufferCallBack, this);
if (ret != SL_RESULT_SUCCESS) {
LOGD("SLAndroidSimpleBufferQueueItf RegisterCallback() failed.");
return ret;
}
return ret;
int64_t getPcmData(void **pcm, FILE *pcmFile, uint8_t *out_buffer) {
while(!feof(pcmFile)) {
size_t size = fread(out_buffer, 1, 44100 * 2 * 2, pcmFile);
*pcm = out_buffer;
return size;
}
return 0;
}
void pcmBufferCallBack(SLAndroidSimpleBufferQueueItf bf, void * context) {
int32_t size = getPcmData(&buffer, pcmFile, out_buffer);
LOGD("pcmBufferCallBack, size=%d", size);
if (NULL != buffer && size > 0) {
SLresult result = (*simpleBufferQueue)->Enqueue(simpleBufferQueue, buffer, size);
}
}
6.6 设置播放状态
void OpenGLESPlayer::start() {
(*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
// 主动调用回调函数开始工作
pcmBufferCallBack(simpleBufferQueue, this);
}
void OpenGLESPlayer::stop() {
LOGD("stop");
(*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_STOPPED);
}
6.7 启动回调函数
void OpenGLESPlayer::start() {
(*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
// 主动调用回调函数开始工作
pcmBufferCallBack(simpleBufferQueue, this);
}
完整示例:Github