Android OpenSL ES 開發:Android OpenSL 介紹和開發流程說明
一、Android OpenSL ES 介紹
OpenSL ES (Open Sound Library for Embedded Systems)是無授權費、跨平臺、針對嵌入式系統精心優化的硬件音頻加速API。它為嵌入式移動多媒體設備上的本地應用程序開發者提供標準化, 高性能,低響應時間的音頻功能實現方法,並實現軟/硬件音頻性能的直接跨平臺部署,降低執行難度,促進高級音頻市場的發展。簡單來說OpenSL ES是一個嵌入式跨平臺免費的音頻處理庫。
Android的OpenSL ES庫是在NDK的platforms文件夾對應android平臺先相應cpu類型裏面,如:
二、Android OpenSL ES 開發流程
OpenSL ES 的開發流程主要有如下6個步驟:
1、 創建接口對象
2、設置混音器
3、創建播放器(錄音器)
4、設置緩沖隊列和回調函數
5、設置播放狀態
6、啟動回調函數
其中4和6是播放PCM等數據格式的音頻是需要用到的。
2.1 OpenSL ES 開發最重要的接口類 SLObjectItf
通過SLObjectItf接口類我們可以創建所需要的各種類型的類接口,比如:
- 創建引擎接口對象:SLObjectItf engineObject
- 創建混音器接口對象:SLObjectItf outputMixObject
- 創建播放器接口對象:SLObjectItf playerObject
以上等等都是通過SLObjectItf來創建的。
2.2 使用SLObjectItf來創建具體的接口對象實例
OpenSL ES中也有具體的接口類,比如(引擎:SLEngineItf,播放器:SLPlayItf,聲音控制器:SLVolumeItf等等)。
2.3 創建引擎並實現
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 }
2.4 利用引擎對象創建其他接口對象
其他接口對象(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 使用示例
首先導入OpenSL ES和其他必須的庫:
-lOpenSLES -landroid
3.1 播放assets文件
創建引擎——>創建混音器——>創建播放器——>設置播放狀態
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; } //第三步,設置播放器參數和創建播放器 // 1、 配置 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}; // 2、 配置 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); }
3.2 播放pcm文件
(集成到ffmpeg時,也是播放ffmpeg轉換成的pcm格式的數據),這裏為了模擬是直接讀取的pcm格式的音頻文件。
1. 創建播放器和混音器
//第一步,創建引擎 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};
2. 設置pcm格式的頻率位數等信息並創建播放器
// 第三步,配置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);
3. 設置緩沖隊列和回調函數
// 註冊回調緩沖區 獲取緩沖隊列接口 (*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; } }
4. 設置播放狀態並手動開始調用回調函數
// 獲取播放狀態接口 (*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)
Android OpenSL ES 開發:Android OpenSL 介紹和開發流程說明