目錄
- OpenSL ES是什么?
- 主要功能
- Android 平臺的OpenSL ES
- 使用OpenSL ES 的優(yōu)點
- API簡要介紹
- 示例
- 參考
1. OpenSL ES是什么?
OpenSL ES(Open Sound Library for Embedded Systems,開源的嵌入式聲音庫)是一個免授權費、跨平臺、C語言編寫的適用于嵌入式系統(tǒng)的硬件加速音頻庫。它為移動和游戲行業(yè)的開發(fā)者提供標準化、高性能、低延遲的方法來實現(xiàn)音頻功能,并致力于跨多個平臺輕松移植應用程序。OpenSL ES由非營利性技術聯(lián)盟Khronos Group管理。
OpenSL ES的設計目標是讓應用程序開發(fā)人員能夠訪問高級音頻功能,如3D定位音頻和MIDI播放,同時努力在制造商和平臺之間輕松實現(xiàn)應用程序移植。
簡要來說 OpenSL ES 是一套針對嵌入式平臺的音頻功能API標準。
2. 主要功能
OpenSL ES主要功能包括:
- 基本音頻播放和錄制。
- 3D音頻效果,包括3D定位音頻。
- 音樂體驗增強效果,包括低音增強和環(huán)境混響。
- 緩沖隊列。
3. Android 平臺的OpenSL ES
Android 2.3將OpenSL ES 1.0.1作為其NDK的一部分。在之后的版本中,實現(xiàn)的延遲有所改進。
Android 實現(xiàn)的 OpenSL ES 只是 OpenSL ES 1.0.1 的子集,并且進行了擴展。因此,對于 OpenSL ES API 的使用,我們需要特別留意哪些是 Android 支持的,哪些是不支持的。
不支持的功能:
- 不支持 MIDI。
- 不支持直接播放 DRM 或者 加密的內容。
- 不支持音頻數(shù)據(jù)的編解碼,如需編解碼,需要使用 MediaCodec API 或者第三方庫。
- 在音頻延時方面,相比于JAVA的 API,并沒有特別明顯地改進。
4. 使用OpenSL ES 的優(yōu)點
- 相比于 Java API,避免音頻數(shù)據(jù)頻繁在 native 層和 Java 層拷貝,提高效率。
- 相比于 Java API,可以更靈活地控制參數(shù)。
- 使用 C 代碼,可以做深度優(yōu)化,比如采用 NEON 優(yōu)化。
5. API簡要介紹
OpenSL ES 雖然是 C 語言編寫,但是它的接口采用的是面向對象的方式,并不是提供一系列的函數(shù)接口,而是以 Interface 的方式來提供 API,這是理解 OpenSL ES API 的一個比較重要的點。
它的大都數(shù) API 需要這樣訪問:
//下面代碼是對 Audio Engine 對象進行 “初始化”
SLEngineItf engineObject;
SLresult result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
如果在 Android NDK 下開發(fā)過 C 代碼,就應該不會太陌生,因為我們調用 “JNI* env” 的函數(shù)也是這個樣子去調用的。
5.1 Object 和 Interface
OpenSL ES 有兩個重要的概念 Object 和 Interface,“對象”和“接口”。
(1) 每個 Object 可能會存在一個或者多個 Interface,官方為每一種 Object 都定義了一系列的 Interface。
(2) 每個 Object 對象都提供了一些最基礎的操作,比如:Realize,Resume,GetState,Destroy 等等,如果希望使用該對象支持的功能函數(shù),則必須通過其 GetInterface 函數(shù)拿到 Interface 接口,然后通過 Interface 來訪問功能函數(shù)。
(3) 并不是每個系統(tǒng)上都實現(xiàn)了 OpenSL ES 為 Object 定義的所有 Interface,所以在獲取 Interface 的時候需要做一些選擇和判斷。
5.2 OpenSL ES的狀態(tài)機制
OpenSL ES 有一個重要的概念:狀態(tài)機制。如圖所示:
任何一個 OpenSL ES 的對象,創(chuàng)建成功后,都進入 SL_OBJECT_STATE_UNREALIZED 狀態(tài),這種狀態(tài)下,系統(tǒng)不會為它分配任何資源。
Realize 后的對象,就會進入 SL_OBJECT_STATE_REALIZED 狀態(tài),這是一種“可用”的狀態(tài),只有在這種狀態(tài)下,對象的各個功能和資源才能正常地訪問。
當一些系統(tǒng)事件發(fā)生后,比如出現(xiàn)錯誤或者 Audio 設備被其他應用搶占,OpenSL ES 對象會進入 SL_OBJECT_STATE_SUSPENDED 狀態(tài),如果希望恢復正常使用,需要調用 Resume 函數(shù)。
當調用對象的 Destroy 函數(shù)后,則會釋放資源,并回到 SL_OBJECT_STATE_UNREALIZED 狀態(tài)。
Engine對象是OpenSL ES API的入口點,這個對象使你能夠創(chuàng)建OpenSL ES中所有其他對象。
Engine對象由全局的對象slCreateEngine()創(chuàng)建得到,創(chuàng)建的結果是Engine對象的一個SLObjectItf的接口。
5.3 Engine Object 和 SLEngineItf Interface
Engine Object是OpenSL ES 里面最核心的對象,
它主要提供如下兩個功能:
(1) 管理 Audio Engine 的生命周期。
(2) 提供管理接口: SLEngineItf,該接口可以用來創(chuàng)建所有其他的 Object 對象。
(3) 提供設備屬性查詢接口:SLEngineCapabilitiesItf 和 SLAudioIODeviceCapabilitiesItf,這些接口可以查詢設備的一些屬性信息。
Engine Object 對象的創(chuàng)建和銷毀的方法如下:
//創(chuàng)建
SLObjectItf engineObject;
slCreateEngine( &engineObject, 0, nullptr, 0, nullptr, nullptr );
//初始化
(*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
//銷毀
(*engineObject)->Destroy(engineObject);
slCreateEngine的函數(shù)定義如下:
SLresult SLAPIENTRY slCreateEngine(
SLObjectItf *pEngine,
SLuint32 numOptions
constSLEngineOption *pEngineOptions,
SLuint32 numInterfaces,
constSLInterfaceID *pInterfaceIds,
constSLboolean *pInterfaceRequired
)
參數(shù)說明如下:
pEngine:指向輸出的engine對象的指針。
numOptions:可選配置數(shù)組的大小。
pEngineOptions:可選配置數(shù)組。
numInterfaces:對象要求支持的接口數(shù)目,不包含隱含的接口。
pInterfaceId:對象需要支持的接口id的數(shù)組。
pInterfaceRequired:指定每個要求接口的接口是可選或者必須的標志位數(shù)組。如果要求的接口沒有實現(xiàn),創(chuàng)建對象會失敗并返回錯誤碼
SL_RESULT_FEATURE_UNSUPPORTED。
獲取管理接口:
SLEngineItf engineEngine;
(*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &(engineEngine));
下面就可以使用 engineEngine 來創(chuàng)建所有 OpenSL ES 的其他對象了。
5.4 Media Object
Media Object代表著多媒體處理功能的抽象,如呈現(xiàn)和捕獲媒體流的對象player、recorder 等等。
可以通過 SLEngineItf 提供的 CreateAudioPlayer 方法來創(chuàng)建一個 player 對象實例,可以通過 SLEngineItf 提供的 CreateAudioRecorder 方法來創(chuàng)建一個 recorder 實例。
5.5 Data Source 和 Data Sink
數(shù)據(jù)源(Data source)是媒體對象的輸入?yún)?shù),指定媒體對象將從何處接收特定類型的數(shù)據(jù)(例如采樣的音頻或MIDI數(shù)據(jù))。 數(shù)據(jù)接收器(Data sink)是媒體對象的輸入?yún)?shù),指定媒體對象將發(fā)送特定類型數(shù)據(jù)的位置。
OpenSL ES 里面,這兩個結構體均是作為創(chuàng)建 Media Object 對象時的參數(shù)而存在的,Data source 代表著輸入源的信息,即數(shù)據(jù)從哪兒來、輸入的數(shù)據(jù)參數(shù)是怎樣的;而 Data Sink 代表著輸出的信息,即數(shù)據(jù)輸出到哪兒、以什么樣的參數(shù)來輸出。
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,或者來自于緩沖區(qū)隊列等等,完全是由 Media Object 對象的具體類型和應用場景來配置。
數(shù)據(jù)格式(data format)標識數(shù)據(jù)流的特征,包括以下幾種類型:
- 基于MIME類型的格式
- PCM格式
5.6 Metadata Extractor Object
播放器對象支持讀取媒體內容的元數(shù)據(jù)。但是有時候只是讀取元數(shù)據(jù)而不播放媒體內容是很有用處的。
Metadata Extractor Object可以用于讀取元數(shù)據(jù)而不需要分配用于媒體播放的資源。
5.7 示例說明
(1) 音頻播放場景:
使用了Audio Player對象來實現(xiàn)播放音頻功能。使用engine對象的 SLEngineItf接口來創(chuàng)建Audio Player,創(chuàng)建之后與Output mix相關聯(lián)用于音頻輸出。輸入以URI作為示例,Output Mix默認與系統(tǒng)相關的默認輸出設備關聯(lián)。
(2) 錄制音頻場景:
通過Audio Recorder對象來實現(xiàn)音頻錄制功能。
6. 示例
OpenSL ES播放PCM數(shù)據(jù)主要有如下7個步驟:
1.創(chuàng)建EngineObject
2.設置DataSource
3.設置DataSink
4.創(chuàng)建播放器
5.設置緩沖隊列和回調函數(shù)
6.設置播放狀態(tài)
7.啟動回調函數(shù)
6.1 創(chuàng)建接口對象
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 創(chuàng)建播放器
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 設置緩沖隊列和回調函數(shù)
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 設置播放狀態(tài)
void OpenGLESPlayer::start() {
(*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
// 主動調用回調函數(shù)開始工作
pcmBufferCallBack(simpleBufferQueue, this);
}
void OpenGLESPlayer::stop() {
LOGD("stop");
(*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_STOPPED);
}
6.7 啟動回調函數(shù)
void OpenGLESPlayer::start() {
(*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
// 主動調用回調函數(shù)開始工作
pcmBufferCallBack(simpleBufferQueue, this);
}
完整示例:Github