開發(fā)Android上的音頻應(yīng)用稠项,最常見的是使用MediaRecorder
和MediaPlayer
來實(shí)現(xiàn)音頻的錄制和播放荞驴,更基礎(chǔ)點(diǎn)的會(huì)使用AudioRecord
和AudioTrack
來實(shí)現(xiàn)烦粒。用這兩種方式已經(jīng)能應(yīng)對(duì)絕大部分的音頻開發(fā)需求了结窘。更底層的API错负,如NDK層的OpenSL ES
則鮮有問津项贺。
最近因?yàn)楣ぷ餍枰佑|了NDK層相關(guān)API寇蚊,這里簡(jiǎn)要記錄下OpenSL ES
相關(guān)的知識(shí)笔时。
關(guān)于OpenSL ES
HelloWorld
不同于傳統(tǒng)的HelloWorld程序,這個(gè)示例稍微復(fù)雜一點(diǎn)仗岸,而且這回我們的實(shí)現(xiàn)允耿,將讓我們聽到這句經(jīng)典的編程入門歡迎語。
實(shí)現(xiàn)思路
- 創(chuàng)建并初始化Audio Engine(音頻引擎较锡,是和底層交互的入口)
- 打開OutputMix(音頻輸出),配置相關(guān)參數(shù)盗痒、Buffer Queue(緩沖隊(duì)列)蚂蕴,以便進(jìn)行音頻播放
- 打開Audio Input(音頻輸入),配置相關(guān)參數(shù),配置Buffer Queue骡楼,以便獲取音頻輸入
- 設(shè)置輸出熔号、輸入的Callback(回調(diào)函數(shù)),實(shí)現(xiàn)將輸入傳給輸出的邏輯
- 啟動(dòng)音頻錄制
也就是鸟整,這個(gè)程序?qū)崿F(xiàn)的功能是:將話筒錄制的聲音跨嘉,再播放出來,也就是返聽的效果吃嘿。
代碼實(shí)現(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接口對(duì)象祠乃,后續(xù)的操作將使用這個(gè)對(duì)象
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兑燥,所以這一步就不去獲取它的接口對(duì)象
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)建音頻播放對(duì)象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)并啟動(dòng)輸入亮瓷、輸出
4.1 配置輸入、輸出回調(diào)
// 輸出回調(diào)
result = *outputBufferQueueInterface)->RegisterCallback(outputBufferQueueInterface, outputCallback, NULL);
// 輸入回調(diào)
result = (*inputBufferQueueInterface)->RegisterCallback(inputBufferQueueInterface, inputCallback, NULL);
4.2 啟動(dòng)輸入輸出
// 設(shè)置為播放狀態(tài)
result = (*audioPlayerInterface)->SetPlayState(audioPlayerInterface, SL_PLAYSTATE_PLAYING);
// 設(shè)為錄制狀態(tài)
result = (*andioRecorderObject)->SetRecordState(andioRecorderObject, SL_RECORDSTATE_RECORDING);
// 啟動(dòng)回調(diào)機(jī)制
(*inputBufferQueueInterface)->Enqueue(inputBufferQueueInterface, inputBuffers[0], buffersize * 4);
(*outputBufferQueueInterface)->Enqueue(outputBufferQueueInterface, outputBuffers[0], buffersize * 4);
4.3 回調(diào)函數(shù)的實(shí)現(xiàn)
// 音頻輸入回調(diào)
static void inputCallback(SLAndroidSimpleBufferQueueItf bufferQueue, void *pContext) {
// 獲取同步鎖
pthread_mutex_lock(&mutex);
// 取一個(gè)可用的緩存
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)者-消費(fèi)者機(jī)制實(shí)現(xiàn)嘱支,當(dāng)輸入可用的時(shí)候,就從輸入的緩沖隊(duì)列里取數(shù)據(jù)出來挣饥,放到inputBuffers里除师;然后當(dāng)輸出準(zhǔn)備就緒的時(shí)候,再?gòu)膇nputBuffers里取出數(shù)據(jù)扔枫,復(fù)制一份汛聚,然后放入輸出的緩沖隊(duì)列里。就這樣實(shí)現(xiàn)了把音頻輸出轉(zhuǎn)到音頻輸出的效果短荐。
關(guān)于OpenSL的使用
使用OpenSL相關(guān)API的通用步驟是:
- 創(chuàng)建對(duì)象(通過帶有create的函數(shù))
- 初始化(通過Realize函數(shù))
- 獲取接口來使用相關(guān)功能(通過GetInterface函數(shù))
OpenSL使用回調(diào)機(jī)制來訪問音頻IO倚舀,但不像跟Jack、CoreAudio那些音頻異步IO框架忍宋,OpenSL 的回調(diào)里并不會(huì)把音頻數(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就緒,這個(gè)方法就應(yīng)被調(diào)用入宦。當(dāng)開啟錄制或開始播放時(shí)哺徊,BufferQueue就可以接受數(shù)據(jù)。這之后云石,回調(diào)機(jī)制通過回調(diào)來告知應(yīng)用程序它已經(jīng)準(zhǔn)備好唉工,可以消費(fèi)(提供)數(shù)據(jù)研乒。
Enqueue
方法可以在回調(diào)里調(diào)用汹忠,可以不。
如果選擇在回調(diào)里調(diào)用,那么在開始播放(錄制)的時(shí)候宽菜,需要先調(diào)用Enqueue
來啟動(dòng)回調(diào)機(jī)制谣膳,否則回調(diào)將不會(huì)被調(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)诡壁,尤其是需要低延遲的場(chǎng)景,如返聽荠割。隨著Android設(shè)備性能的提升妹卿,以及Android系統(tǒng)的不斷優(yōu)化,音頻延遲的問題已經(jīng)有了可觀的性能提升蔑鹦。而在游戲領(lǐng)域夺克,OpenSL ES
的高性能也能提供更棒的游戲體驗(yàn),甚至讓移動(dòng)平臺(tái)也有打造《吟誦者(In Verbis Virtus)》這種語音類游戲的可能嚎朽。
But not ALL
對(duì)于OpenSL ES
也僅僅是草草接觸铺纽,如有不對(duì)或疏漏的地方,還請(qǐng)大家指正哟忍。