開發(fā)Android上的音頻應(yīng)用,最常見的是使用MediaRecorder和MediaPlayer來實現(xiàn)音頻的錄制和播放,更基礎(chǔ)點的會使用AudioRecord和AudioTrack來實現(xiàn)。用這兩種方式已經(jīng)能應(yīng)對絕大部分的音頻開發(fā)需求了。更底層的API,如NDK層的OpenSL ES則鮮有問津。
最近因為工作需要,接觸了NDK層相關(guān)API,這里簡要記錄下OpenSL ES相關(guān)的知識。
關(guān)于OpenSL ES
HelloWorld
不同于傳統(tǒng)的HelloWorld程序,這個示例稍微復(fù)雜一點,而且這回我們的實現(xiàn),將讓我們聽到這句經(jīng)典的編程入門歡迎語。
實現(xiàn)思路
- 創(chuàng)建并初始化Audio Engine(音頻引擎,是和底層交互的入口)
- 打開OutputMix(音頻輸出),配置相關(guān)參數(shù)、Buffer Queue(緩沖隊列),以便進行音頻播放
- 打開Audio Input(音頻輸入),配置相關(guān)參數(shù),配置Buffer Queue,以便獲取音頻輸入
- 設(shè)置輸出、輸入的Callback(回調(diào)函數(shù)),實現(xiàn)將輸入傳給輸出的邏輯
- 啟動音頻錄制
也就是,這個程序?qū)崿F(xiàn)的功能是:將話筒錄制的聲音,再播放出來,也就是返聽的效果。
代碼實現(xiàn)
1. 創(chuàng)建并初始化Audio Engine
// 創(chuàng)建Audio Engine
result = slCreateEngine(&openSLEngine, 0, NULL, 0, NULL, NULL);
// 初始化上一步得到的openSLEngine
result = (*openSLEngine)->Realize(openSLEngine, SL_BOOLEAN_FALSE);
// 獲取SLEngine接口對象,后續(xù)的操作將使用這個對象
SLEngineItf openSLEngineInterface = NULL;
result = (*openSLEngine)->GetInterface(openSLEngine, SL_IID_ENGINE, &openSLEngineInterface);
2. 音頻輸出
2.1 打開音頻輸出設(shè)備
// 相關(guān)參數(shù)
const SLInterfaceID ids[] = {SL_IID_VOLUME};
const SLboolean req[] = {SL_BOOLEAN_FALSE};
// 使用第一步的openSLEngineInterface,創(chuàng)建音頻輸出Output Mix
result = (*openSLEngineInterface)->CreateOutputMix(openSLEngineInterface, &outputMix, 0, ids, req);
// 初始化outputMix
result = (*outputMix)->Realize(outputMix, SL_BOOLEAN_FALSE);
// 由于不需要操作到ouputMix,所以這一步就不去獲取它的接口對象
2.2 配置相關(guān)參數(shù)
// Buffer Queue的參數(shù)
SLDataLocator_AndroidSimpleBufferQueue outputLocator = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 };
// 設(shè)置音頻格式
SLDataFormat_PCM outputFormat = { SL_DATAFORMAT_PCM, 2, samplerate, SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN };
// 輸出源
SLDataSource outputSource = { &outputLocator, &outputFormat };
// 輸出管道
SLDataLocator_OutputMix outputMixLocator = { SL_DATALOCATOR_OUTPUTMIX, outputMix };
SLDataSink outputSink = { &outputMixLocator, NULL };
2.3 創(chuàng)建播放器
// 參數(shù)
const SLInterfaceID outputInterfaces[1] = { SL_IID_BUFFERQUEUE };
const SLboolean requireds[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE };
// 創(chuàng)建音頻播放對象AudioPlayer
result = (*openSLEngineInterface)->CreateAudioPlayer(openSLEngineInterface, &audioPlayerObject, &outputSource, &outputSink, 1, outputInterfaces, requireds);
// 初始化AudioPlayer
result = (*audioPlayerObject)->Realize(audioPlayerObject, SL_BOOLEAN_FALSE);
// 獲取音頻輸出的BufferQueue接口
SLAndroidSimpleBufferQueueItf outputBufferQueueInterface = NULL;
result = (*audioPlayerObject)->GetInterface(audioPlayerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &outputBufferQueueInterface);
// 獲取播放器接口
SLPlayItf outputPlayInterface;
result = (*audioPlayerObject)->GetInterface(audioPlayerObject, SL_IID_PLAY, &audioPlayerInterface);
3. 音頻輸入
3.1 配置參數(shù)
// 參數(shù)
SLDataLocator_IODevice deviceInputLocator = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, NULL };
SLDataSource inputSource = { &deviceInputLocator, NULL };
SLDataLocator_AndroidSimpleBufferQueue inputLocator = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 };
SLDataFormat_PCM inputFormat = { SL_DATAFORMAT_PCM, 2, samplerate * 1000, SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN };
SLDataSink inputSink = { &inputLocator, &inputFormat };
const SLInterfaceID inputInterfaces[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION };
const SLboolean requireds[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE };
3.2 創(chuàng)建錄制器
// 創(chuàng)建AudioRecorder
result = (*openSLEngineInterface)->CreateAudioRecorder(openSLEngineInterface, &andioRecorderObject, &inputSource, &inputSink, 2, inputInterfaces, requireds);
// 初始化AudioRecorder
result = (*andioRecorderObject)->Realize(andioRecorderObject, SL_BOOLEAN_FALSE);
// 獲取音頻輸入的BufferQueue接口
result = (*andioRecorderObject)->GetInterface(andioRecorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &inputBufferQueueInterface);
// 獲取錄制器接口
SLRecordItf audioRecorderInterface;
(*andioRecorderObject)->GetInterface(andioRecorderObject, SL_IID_RECORD, &audioRecorderInterface);
4. 配置回調(diào)并啟動輸入、輸出
4.1 配置輸入、輸出回調(diào)
// 輸出回調(diào)
result = *outputBufferQueueInterface)->RegisterCallback(outputBufferQueueInterface, outputCallback, NULL);
// 輸入回調(diào)
result = (*inputBufferQueueInterface)->RegisterCallback(inputBufferQueueInterface, inputCallback, NULL);
4.2 啟動輸入輸出
// 設(shè)置為播放狀態(tài)
result = (*audioPlayerInterface)->SetPlayState(audioPlayerInterface, SL_PLAYSTATE_PLAYING);
// 設(shè)為錄制狀態(tài)
result = (*andioRecorderObject)->SetRecordState(andioRecorderObject, SL_RECORDSTATE_RECORDING);
// 啟動回調(diào)機制
(*inputBufferQueueInterface)->Enqueue(inputBufferQueueInterface, inputBuffers[0], buffersize * 4);
(*outputBufferQueueInterface)->Enqueue(outputBufferQueueInterface, outputBuffers[0], buffersize * 4);
4.3 回調(diào)函數(shù)的實現(xiàn)
// 音頻輸入回調(diào)
static void inputCallback(SLAndroidSimpleBufferQueueItf bufferQueue, void *pContext) {
// 獲取同步鎖
pthread_mutex_lock(&mutex);
// 取一個可用的緩存
short int *inputBuffer = inputBuffers[inputBufferWrite];
if (inputBuffersAvailable == 0)
inputBufferRead = inputBufferWrite;
// 可用緩存+1
inputBuffersAvailable++;
if (inputBufferWrite < numBuffers - 1)
inputBufferWrite++;
else
inputBufferWrite = 0;
pthread_mutex_unlock(&mutex);
// 調(diào)用BufferQueue的Enqueue方法,把輸入數(shù)據(jù)取到inputBuffer
(*bufferQueue)->Enqueue(bufferQueue, inputBuffer, buffersize * 4);
}
// 音頻輸出回調(diào)
static void outputCallback(SLAndroidSimpleBufferQueueItf bufferQueue, void *pContext) {
short int *outputBuffer = outputBuffers[outputBufferIndex];
pthread_mutex_lock(&mutex);
if (inputBuffersAvailable < 1) {
pthread_mutex_unlock(&mutex);
memset(outputBuffer, 0, buffersize * 4);
} else {
short int *inputBuffer = inputBuffers[inputBufferRead];
if (inputBufferRead < numBuffers - 1) inputBufferRead++; else inputBufferRead = 0;
inputBuffersAvailable--;
pthread_mutex_unlock(&mutex);
memcpy(outputBuffer, inputBuffer, buffersize * 4);
}
(*bufferQueue)->Enqueue(bufferQueue, outputBuffer, buffersize * 4);
if (outputBufferIndex < numBuffers - 1) outputBufferIndex++; else outputBufferIndex = 0;
}
回調(diào)函數(shù)使用生產(chǎn)者-消費者機制實現(xiàn),當(dāng)輸入可用的時候,就從輸入的緩沖隊列里取數(shù)據(jù)出來,放到inputBuffers里;然后當(dāng)輸出準(zhǔn)備就緒的時候,再從inputBuffers里取出數(shù)據(jù),復(fù)制一份,然后放入輸出的緩沖隊列里。就這樣實現(xiàn)了把音頻輸出轉(zhuǎn)到音頻輸出的效果。
關(guān)于OpenSL的使用
使用OpenSL相關(guān)API的通用步驟是:
- 創(chuàng)建對象(通過帶有create的函數(shù))
- 初始化(通過Realize函數(shù))
- 獲取接口來使用相關(guān)功能(通過GetInterface函數(shù))
OpenSL使用回調(diào)機制來訪問音頻IO,但不像跟Jack、CoreAudio那些音頻異步IO框架,OpenSL 的回調(diào)里并不會把音頻數(shù)據(jù)作為參數(shù)傳遞,回調(diào)方法僅僅是告訴我們:BufferQueue已經(jīng)就緒,可以接受/獲取數(shù)據(jù)了。
使用SLBufferQueueItf. Enqueue函數(shù)從(往)音頻設(shè)備獲取(放入)數(shù)據(jù)。完整的函數(shù)簽名是:SLresult (*Enqueue) (SLBufferQueueItf self, const void *pBuffer, SLuint32 size);
當(dāng)BufferQueue就緒,這個方法就應(yīng)被調(diào)用。當(dāng)開啟錄制或開始播放時,BufferQueue就可以接受數(shù)據(jù)。這之后,回調(diào)機制通過回調(diào)來告知應(yīng)用程序它已經(jīng)準(zhǔn)備好,可以消費(提供)數(shù)據(jù)。
Enqueue方法可以在回調(diào)里調(diào)用,可以不。
如果選擇在回調(diào)里調(diào)用,那么在開始播放(錄制)的時候,需要先調(diào)用Enqueue來啟動回調(diào)機制,否則回調(diào)將不會被調(diào)用到。
如果選擇不在回調(diào)里調(diào)用,回調(diào)則用于通知程序,它準(zhǔn)備就緒了。程序可以在得到足夠的數(shù)據(jù)緩存之后,再把數(shù)據(jù)給它處理。這示例使用的是前一種方式,在回調(diào)里調(diào)用Enqueue。
That's all
使用OpenSL ES可以更高效的使用Android的音頻系統(tǒng),尤其是需要低延遲的場景,如返聽。隨著Android設(shè)備性能的提升,以及Android系統(tǒng)的不斷優(yōu)化,音頻延遲的問題已經(jīng)有了可觀的性能提升。而在游戲領(lǐng)域,OpenSL ES的高性能也能提供更棒的游戲體驗,甚至讓移動平臺也有打造《吟誦者(In Verbis Virtus)》這種語音類游戲的可能。
But not ALL
對于OpenSL ES也僅僅是草草接觸,如有不對或疏漏的地方,還請大家指正。