目錄
- 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ā)者提供標準化扯键、高性能珊肃、低延遲的方法來實現音頻功能伦乔,并致力于跨多個平臺輕松移植應用程序。OpenSL ES由非營利性技術聯(lián)盟Khronos Group管理。
OpenSL ES的設計目標是讓應用程序開發(fā)人員能夠訪問高級音頻功能恬试,如3D定位音頻和MIDI播放疯暑,同時努力在制造商和平臺之間輕松實現應用程序移植缰儿。
簡要來說 OpenSL ES 是一套針對嵌入式平臺的音頻功能API標準乖阵。
2. 主要功能
OpenSL ES主要功能包括:
- 基本音頻播放和錄制。
- 3D音頻效果儒将,包括3D定位音頻对蒲。
- 音樂體驗增強效果,包括低音增強和環(huán)境混響鸣驱。
- 緩沖隊列蝠咆。
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 的優(yōu)點
- 相比于 Java API,避免音頻數據頻繁在 native 層和 Java 層拷貝外遇,提高效率契吉。
- 相比于 Java API捐晶,可以更靈活地控制參數。
- 使用 C 代碼山上,可以做深度優(yōu)化英支,比如采用 NEON 優(yōu)化。
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 下開發(fā)過 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) 并不是每個系統(tǒng)上都實現了 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ā)生后,比如出現錯誤或者 Audio 設備被其他應用搶占步责,OpenSL ES 對象會進入 SL_OBJECT_STATE_SUSPENDED 狀態(tài)蔓肯,如果希望恢復正常使用振乏,需要調用 Resume 函數。
當調用對象的 Destroy 函數后调限,則會釋放資源耻矮,并回到 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的函數定義如下:
SLresult SLAPIENTRY slCreateEngine(
SLObjectItf *pEngine,
SLuint32 numOptions
constSLEngineOption *pEngineOptions,
SLuint32 numInterfaces,
constSLInterfaceID *pInterfaceIds,
constSLboolean *pInterfaceRequired
)
參數說明如下:
pEngine:指向輸出的engine對象的指針俱病。
numOptions:可選配置數組的大小袱结。
pEngineOptions:可選配置數組垢夹。
numInterfaces:對象要求支持的接口數目棚饵,不包含隱含的接口掩完。
pInterfaceId:對象需要支持的接口id的數組。
pInterfaceRequired:指定每個要求接口的接口是可選或者必須的標志位數組欣硼。如果要求的接口沒有實現诈胜,創(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代表著多媒體處理功能的抽象缓熟,如呈現和捕獲媒體流的對象player、recorder 等等够滑。
可以通過 SLEngineItf 提供的 CreateAudioPlayer 方法來創(chuàng)建一個 player 對象實例彰触,可以通過 SLEngineItf 提供的 CreateAudioRecorder 方法來創(chuàng)建一個 recorder 實例。
5.5 Data Source 和 Data Sink
數據源(Data source)是媒體對象的輸入參數分蓖,指定媒體對象將從何處接收特定類型的數據(例如采樣的音頻或MIDI數據)咆疗。 數據接收器(Data sink)是媒體對象的輸入參數母债,指定媒體對象將發(fā)送特定類型數據的位置毡们。
OpenSL ES 里面,這兩個結構體均是作為創(chuàng)建 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,或者來自于緩沖區(qū)隊列等等讨勤,完全是由 Media Object 對象的具體類型和應用場景來配置箭跳。
數據格式(data format)標識數據流的特征,包括以下幾種類型:
- 基于MIME類型的格式
- PCM格式
5.6 Metadata Extractor Object
播放器對象支持讀取媒體內容的元數據潭千。但是有時候只是讀取元數據而不播放媒體內容是很有用處的谱姓。
Metadata Extractor Object可以用于讀取元數據而不需要分配用于媒體播放的資源。
5.7 示例說明
(1) 音頻播放場景:
使用了Audio Player對象來實現播放音頻功能刨晴。使用engine對象的 SLEngineItf接口來創(chuàng)建Audio Player屉来,創(chuàng)建之后與Output mix相關聯(lián)用于音頻輸出。輸入以URI作為示例狈癞,Output Mix默認與系統(tǒng)相關的默認輸出設備關聯(lián)。
(2) 錄制音頻場景:
通過Audio Recorder對象來實現音頻錄制功能蝶桶。
6. 示例
OpenSL ES播放PCM數據主要有如下7個步驟:
1.創(chuàng)建EngineObject
2.設置DataSource
3.設置DataSink
4.創(chuàng)建播放器
5.設置緩沖隊列和回調函數
6.設置播放狀態(tài)
7.啟動回調函數
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 設置緩沖隊列和回調函數
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);
// 主動調用回調函數開始工作
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