初探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 Android
OpenSLES官網
相關文章
WAV檔案格式學習心得
初探XAudio2
初探OpenAL