OpenSL ES (Open Sound Library for Embedded Systems)是無受權費、跨平臺、針對嵌入式系統精心優化的硬件音頻加速API。它爲嵌入式移動多媒體設備上的本地應用程序開發者提供標準化, 高性能,低響應時間的音頻功能實現方法,並實現軟/硬件音頻性能的直接跨平臺部署,下降執行難度,促進高級音頻市場的發展。簡單來講OpenSL ES是一個嵌入式跨平臺免費的音頻處理庫。 android
Android的OpenSL ES庫是在NDK的platforms文件夾對應android平臺先相應cpu類型裏面,如:函數
OpenSL ES 的開發流程主要有以下6個步驟:性能
一、 建立接口對象優化
二、設置混音器ui
三、建立播放器(錄音器)this
四、設置緩衝隊列和回調函數spa
五、設置播放狀態code
六、啓動回調函數orm
註明:其中第4步和第6步是OpenSL ES 播放PCM等數據格式的音頻是須要用到的。對象
在使用OpenSL ES的API以前,須要引入OpenSL ES的頭文件,代碼以下:
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
因爲是在Native層使用該特性,所需須要在Android.mk中增長連接選項,以便在連接階段使用到系統系統的OpenSL ES的so庫:
LOCAL_LDLIBS += -lOepnSLES
咱們知道OpenSL ES提供的是基於C語言的API,可是它是基於對象和接口的方式提供的,會採用面向對象的思想開發API。所以咱們先來了解一下OpenSL ES中對象和接口的概念:
須要重點理解的是,一個對象在代碼中實際上是沒有實際的表示形式的,能夠經過接口來改變對象的狀態以及使用對象提供的功能。對象有能夠有一個或者多個接口的實例,可是接口實例確定只屬於一個對象。
若是明白了OpenSL ES 中對象和接口的概念,那麼下面咱們就繼續看看,在代碼中是如何使用它們的。
上面咱們也提到過,對象是沒有實際的代碼表示形式的,對象的建立也是經過接口來完成的。經過獲取對象的方法來獲取出對象,進而能夠訪問對象的其餘的接口方法或者改變對象的狀態,下面是使用對象和接口的相關說明。
經過SLObjectItf接口類咱們能夠建立所須要的各類類型的類接口,好比:
以上等等都是經過SLObjectItf來建立的。
OpenSL ES中也有具體的接口類,好比(引擎:SLEngineItf,播放器:SLPlayItf,聲音控制器:SLVolumeItf等等)。
OpenSL ES中開始的第一步都是聲明SLObjectItf接口類型的引擎接口對象engineObject,而後用方法slCreateEngine建立一個引擎接口對象;建立好引擎接口對象後,須要用SLObjectItf的Realize方法來實現engineObject;最後用SLObjectItf的GetInterface方法來初始化SLEngnineItf對象實例。如:
SLObjectItf engineObject = NULL;//用SLObjectItf聲明引擎接口對象 SLEngineItf engineEngine = NULL;//聲明具體的引擎對象實例 void createEngine() { SLresult result;//返回結果 result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);//第一步建立引擎 result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);//實現(Realize)engineObject接口對象 result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);//經過engineObject的GetInterface方法初始化engineEngine }
其餘接口對象(SLObjectItf outputMixObject,SLObjectItf playerObject)等都是用引擎接口對象建立的(具體的接口對象須要的參數這裏就說了,可參照ndk例子裏面的),如:
//混音器 SLObjectItf outputMixObject = NULL;//用SLObjectItf建立混音器接口對象 SLEnvironmentalReverbItf outputMixEnvironmentalReverb = NULL;////建立具體的混音器對象實例 result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, mids, mreq);//利用引擎接口對象建立混音器接口對象 result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);//實現(Realize)混音器接口對象 result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB, &outputMixEnvironmentalReverb);//利用混音器接口對象初始化具體混音器實例 //播放器 SLObjectItf playerObject = NULL;//用SLObjectItf建立播放器接口對象 SLPlayItf playerPlay = NULL;//建立具體的播放器對象實例 result = (*engineEngine)->CreateAudioPlayer(engineEngine, &playerObject, &audioSrc, &audioSnk, 3, ids, req);//利用引擎接口對象建立播放器接口對象 result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);//實現(Realize)播放器接口對象 result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay);//初始化具體的播放器對象實例
最後就是使用建立好的具體對象實例來實現具體的功能。
首先導入OpenSL ES和其餘必須的庫:
-lOpenSLES -landroid
建立引擎——>建立混音器——>建立播放器——>設置播放狀態
JNIEXPORT void JNICALL Java_com_renhui_openslaudio_MainActivity_playAudioByOpenSL_1assets(JNIEnv *env, jobject instance, jobject assetManager, jstring filename) { release(); const char *utf8 = (*env)->GetStringUTFChars(env, filename, NULL); // use asset manager to open asset by filename AAssetManager* mgr = AAssetManager_fromJava(env, assetManager); AAsset* asset = AAssetManager_open(mgr, utf8, AASSET_MODE_UNKNOWN); (*env)->ReleaseStringUTFChars(env, filename, utf8); // open asset as file descriptor off_t start, length; int fd = AAsset_openFileDescriptor(asset, &start, &length); AAsset_close(asset); SLresult result; //第一步,建立引擎 createEngine(); //第二步,建立混音器 const SLInterfaceID mids[1] = {SL_IID_ENVIRONMENTALREVERB}; const SLboolean mreq[1] = {SL_BOOLEAN_FALSE}; result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, mids, mreq); (void)result; result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); (void)result; result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB, &outputMixEnvironmentalReverb); if (SL_RESULT_SUCCESS == result) { result = (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(outputMixEnvironmentalReverb, &reverbSettings); (void)result; } //第三步,設置播放器參數和建立播放器 // 一、 配置 audio source 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}; // 二、 配置 audio sink SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject}; SLDataSink audioSnk = {&loc_outmix, NULL}; // 建立播放器 const SLInterfaceID ids[3] = {SL_IID_SEEK, SL_IID_MUTESOLO, SL_IID_VOLUME}; const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; result = (*engineEngine)->CreateAudioPlayer(engineEngine, &fdPlayerObject, &audioSrc, &audioSnk, 3, ids, req); (void)result; // 實現播放器 result = (*fdPlayerObject)->Realize(fdPlayerObject, SL_BOOLEAN_FALSE); (void)result; // 獲得播放器接口 result = (*fdPlayerObject)->GetInterface(fdPlayerObject, SL_IID_PLAY, &fdPlayerPlay); (void)result; // 獲得聲音控制接口 result = (*fdPlayerObject)->GetInterface(fdPlayerObject, SL_IID_VOLUME, &fdPlayerVolume); (void)result; //第四步,設置播放狀態 if (NULL != fdPlayerPlay) { result = (*fdPlayerPlay)->SetPlayState(fdPlayerPlay, SL_PLAYSTATE_PLAYING); (void)result; } //設置播放音量 (100 * -50:靜音 ) (*fdPlayerVolume)->SetVolumeLevel(fdPlayerVolume, 20 * -50); }
(集成到ffmpeg時,也是播放ffmpeg轉換成的pcm格式的數據),這裏爲了模擬是直接讀取的pcm格式的音頻文件。
//第一步,建立引擎 createEngine(); //第二步,建立混音器 const SLInterfaceID mids[1] = {SL_IID_ENVIRONMENTALREVERB}; const SLboolean mreq[1] = {SL_BOOLEAN_FALSE}; result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, mids, mreq); (void)result; result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); (void)result; result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB, &outputMixEnvironmentalReverb); if (SL_RESULT_SUCCESS == result) { result = (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties( outputMixEnvironmentalReverb, &reverbSettings); (void)result; } SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject}; SLDataSink audioSnk = {&outputMix, NULL};
// 第三步,配置PCM格式信息 SLDataLocator_AndroidSimpleBufferQueue android_queue={SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,2}; SLDataFormat_PCM pcm={ SL_DATAFORMAT_PCM,//播放pcm格式的數據 2,//2個聲道(立體聲) SL_SAMPLINGRATE_44_1,//44100hz的頻率 SL_PCMSAMPLEFORMAT_FIXED_16,//位數 16位 SL_PCMSAMPLEFORMAT_FIXED_16,//和位數一致就行 SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,//立體聲(前左前右) SL_BYTEORDER_LITTLEENDIAN//結束標誌 }; SLDataSource slDataSource = {&android_queue, &pcm}; const SLInterfaceID ids[3] = {SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND, SL_IID_VOLUME}; const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; result = (*engineEngine)->CreateAudioPlayer(engineEngine, &pcmPlayerObject, &slDataSource, &audioSnk, 3, ids, req); //初始化播放器 (*pcmPlayerObject)->Realize(pcmPlayerObject, SL_BOOLEAN_FALSE); // 獲得接口後調用 獲取Player接口 (*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_PLAY, &pcmPlayerPlay);
// 註冊回調緩衝區 獲取緩衝隊列接口 (*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_BUFFERQUEUE, &pcmBufferQueue); //緩衝接口回調 (*pcmBufferQueue)->RegisterCallback(pcmBufferQueue, pcmBufferCallBack, NULL);
回調函數:
void * pcmBufferCallBack(SLAndroidBufferQueueItf bf, void * context) { //assert(NULL == context); getPcmData(&buffer); // for streaming playback, replace this test by logic to find and fill the next buffer if (NULL != buffer) { SLresult result; // enqueue another buffer result = (*pcmBufferQueue)->Enqueue(pcmBufferQueue, buffer, 44100 * 2 * 2); // the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT, // which for this code example would indicate a programming error } }
讀取pcm格式的文件:
void getPcmData(void **pcm) { while(!feof(pcmFile)) { fread(out_buffer, 44100 * 2 * 2, 1, pcmFile); if(out_buffer == NULL) { LOGI("%s", "read end"); break; } else{ LOGI("%s", "reading"); } *pcm = out_buffer; break; } }
// 獲取播放狀態接口 (*pcmPlayerPlay)->SetPlayState(pcmPlayerPlay, SL_PLAYSTATE_PLAYING); // 主動調用回調函數開始工做 pcmBufferCallBack(pcmBufferQueue, NULL);
注意:
在回調函數中result = (*pcmBufferQueue)->Enqueue(pcmBufferQueue, buffer, 44100 * 2 * 2),最後的「44100*2*2」是buffer的大小,由於我這裏是指定了沒讀取一次就從pcm文件中讀取了「44100*2*2」個字節,因此能夠正常播放,若是是利用ffmpeg來獲取pcm數據源,那麼實際大小要根據每一個AVframe的具體大小來定,這樣才能正常播放出聲音!(44100 * 2 * 2 表示:44100是頻率HZ,2是立體聲雙通道,2是採用的16位採樣即2個字節,因此總的字節數就是:44100 * 2 * 2)