初探OpenSLES
前言
在Android平台系統提供的音效API為OpenSLES,不過可以在GitHub找到OpenAL in Android,在評估過後,我還是決定使用系統提供的,在此把學習過程做個紀錄。內容
在評估在Android時到底要用OpenAL還是OpenSLES時,由於擔心OpenAL的部分不是官方所提供的,如果有問題很難得到支援,所以就選了OpenSLES,但OpenSLES的用法和OpenAL或XAudio2相近嗎?是的,至少在Stream play的流程是一樣的,整合起來不會有太大的問題。OpenSLES的範例並不多,目前的參考範例為NDK的範例。在開始Stream play前,和OpenAL與XAudio2一樣要初始化,具體的範例如下
#include <SLES/OpenSLES.h> #include <SLES/OpenSLES_Android.h> int main() { // //... // SLObjectItf engineObject; SLEngineItf engineInterface; SLObjectItf outputMixObject; SLEngineOption engineOption[]= { { (SLuint32)SL_ENGINEOPTION_THREADSAFE, (SLuint32)SL_BOOLEAN_TRUE } }; SLresult res=slCreateEngine(&engineObject,1,engineOption,0,NULL,NULL); if(res!=SL_RESULT_SUCCESS) return 1; // res=(*engineObject)->Realize(engineObject,SL_BOOLEAN_FALSE); if(res!=SL_RESULT_SUCCESS) return 1; // res=(*engineObject)->GetInterface(engineObject,SL_IID_ENGINE,&engineInterface); if(res!=SL_RESULT_SUCCESS) return 1; // res=(*engineInterface)->CreateOutputMix(engineInterface,&outputMixObject,0,NULL,NULL); if(res!=SL_RESULT_SUCCESS) return 1; // res=(*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); if(res!=SL_RESULT_SUCCESS) return 1; // //... // (*outputMixObject)->Destroy(outputMixObject); (*engineObject)->Destroy(engineObject); return 0; }
先來說說OpenSLES的API使用習慣,SLObjectItf的物件在經過Create後,一定要Realize()才會生效,這點一定要注意!釋放的部分就比較簡單,直接喚起Destroy()即可,接著是介面物件,可以想像是物件的Public funciton,不過必須透過GetInterface()來取得,介面物件並不需要做釋放的動作。EngineObject的Create是透過slCreateEngine()來建立,建立後喚起Realize(),接著取得該物件的介面,也就是EngineInterface,接著透過engine interface來Create OutputMixObject,一樣,OutputMixObject在Create後馬上喚起Realize(),這樣就初始化完成了。釋放的部分相當簡單,對個別物件喚起Destroy()即可。
接著來說明Stream play的實現,先看看範例
#include <SLES/OpenSLES.h> #include <SLES/OpenSLES_Android.h> bool g_IsPlaying=false; void PlayerPlayCallback(SLPlayItf caller,void* pContext, SLuint32 iEvent) { // play finish if(iEvent==SL_PLAYEVENT_HEADATEND) { //On play end... g_IsPlaying=false; } } void PlayerBufferQueueCallBack(SLAndroidSimpleBufferQueueItf playerBufferQueueInterface,void* pContext) { // //Enqueue next buffer... } int main() { // //... // SLObjectItf engineObject; SLEngineItf engineInterface; SLObjectItf outputMixObject; SLEngineOption engineOption[]= { { (SLuint32)SL_ENGINEOPTION_THREADSAFE, (SLuint32)SL_BOOLEAN_TRUE } }; SLresult res=slCreateEngine(&engineObject,1,engineOption,0,NULL,NULL); if(res!=SL_RESULT_SUCCESS) return 1; // res=(*engineObject)->Realize(engineObject,SL_BOOLEAN_FALSE); if(res!=SL_RESULT_SUCCESS) return 1; // res=(*engineObject)->GetInterface(engineObject,SL_IID_ENGINE,&engineInterface); if(res!=SL_RESULT_SUCCESS) return 1; // res=(*engineInterface)->CreateOutputMix(engineInterface,&outputMixObject,0,NULL,NULL); if(res!=SL_RESULT_SUCCESS) return 1; // res=(*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); if(res!=SL_RESULT_SUCCESS) return 1; // //Follow data load from WAV file WAVEFORMATEX format; unsigned char* pWAVRawData; size_t wavRawDataSize; // SLObjectItf playerObject; SLPlayItf playerPlayInterface; SLAndroidSimpleBufferQueueItf playerBufferQueueInterface; SLDataLocator_AndroidSimpleBufferQueue locatorBufferQueue= {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,2}; SLuint32 tagBitsPerSample=waveFormat.wBitsPerSample*waveFormat.nChannels; SLDataFormat_PCM formatPCM= { SL_DATAFORMAT_PCM, waveFormat.nChannels, waveFormat.nSamplesPerSec*1000, tagBitsPerSample, tagBitsPerSample, SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN }; SLDataSource audioSource={&locatorBufferQueue,&formatPCM}; SLDataLocator_OutputMix locatorOutmix={SL_DATALOCATOR_OUTPUTMIX,outputMixObject }; SLDataSink audioSink={&locatorOutmix,NULL}; SLInterfaceID interfaceIDList[]={SL_IID_BUFFERQUEUE,SL_IID_VOLUME, SL_IID_EFFECTSEND}; SLboolean requiredList[] = {SL_BOOLEAN_TRUE,SL_BOOLEAN_TRUE,SL_BOOLEAN_TRUE}; res=(*engineInterface)->CreateAudioPlayer(engineInterface,&playerObject,&audioSource,&audioSink,2,interfaceIDList,requiredList); if(res!=SL_RESULT_SUCCESS) return 1; res=(*playerObject)->Realize(playerObject,SL_BOOLEAN_FALSE); if(res!=SL_RESULT_SUCCESS) return 1; res=(*playerObject)->GetInterface(playerObject,SL_IID_PLAY,&playerPlayInterface); if(res!=SL_RESULT_SUCCESS) return 1; res=(*playerPlayInterface)->RegisterCallBack(playerPlayInterface,PlayerPlayCallback,NULL); if(res!=SL_RESULT_SUCCESS) return 1; res=(*playerPlayInterface)->SetCallbackEventsMask(playerPlayInterface,SL_PLAYEVENT_HEADATEND); if(res!=SL_RESULT_SUCCESS) return 1; res=(*playerObject)->GetInterface(playerObject,SL_IID_BUFFERQUEUE,&playerBufferQueueInterface); if(res!=SL_RESULT_SUCCESS) return 1; res=(*playerBufferQueueInterface)->RegisterCallback(playerBufferQueueInterface,PlayerBufferQueueCallBack,NULL); if(res!=SL_RESULT_SUCCESS) return 1; // res=(*playerBufferQueueInterface)->Enqueue(playerBufferQueueInterface,pWAVRawData,wavRawDataSize); if(res!=SL_RESULT_SUCCESS) return 1; g_IsPlaying=true; res=(*playerPlayInterface)->SetPlayState(playerPlayInterface,SL_PLAYSTATE_PLAYING); if(res!=SL_RESULT_SUCCESS) return 1; // while(g_IsPlaying) ::Sleeping(1)//sleep 1ms // (*playerObject)->Destroy(playerObject); (*outputMixObject)->Destroy(outputMixObject); (*engineObject)->Destroy(engineObject); return 0; }
範例碼很長,但其實是因為OpenSLES的使用習慣造成看起來很長,和OpenAL與XAudio2一樣Create一個播放元件,OpenSLES的播放元件是"AudioPlayer",初始化的參數很多,要注意的只有SLDataFormat_PCM,裡面設定的是播放資料的格式,其餘的請照抄 ,AudioPlayer在Create後一樣要Realize(),接著是取得PlayerPlayInterface與PlayerBufferQueueInterface,PlayerPlayInterface控制的是播放與停止,還要注意RegisterCallBack()與SetCallbackEventsMask(),這兩個步驟,這兩個步驟是要在播放器播完聲音後喚起起一個CallBack,範例的話就是PlayerPlayCallback(),PlayerBufferQueueInterface控制的是Stream play的queue,和XAudio2一樣不需要自己Dequeue,但要透過RegisterCallback()來註冊CallBack,這個CallBack會在每處理完一個Buffer後喚起,在範例的話就是PlayerBufferQueueCallBack(),PlayerPlayCallback()與PlayerBufferQueueCallBack()都可以看到"pContext"的參數,也可以看到RegisterCallback()的最後一個參數,範例是都傳NULL,這個部分可以依據需要傳入物件的指標,在CallBack的時候再將pContext做轉型來做到傳遞物件,範例在要Equeue next buffer的部分是空的,請依據需要傳入物件指標來完成,Enqueue的部分可以用PlayerBufferQueueInterface來實現。
整體來說,OpenSLES的程式碼會看起來很長,因為使用習慣的關係,在Stream play的部分,有一些麻煩,就是要規劃Enqueue next buffer的規格與規劃播放結束的處理。加上先前的初探XAudio2與初探OpenAL來參考,就可以寫出整合這3套API的Stream play。
參考資料
OpenAL in AndroidOpenSLES官網
相關文章
WAV檔案格式學習心得初探XAudio2
初探OpenAL
沒有留言:
張貼留言