Android音頻開發(fā)之OpenSL ES

開發(fā)Android上的音頻應(yīng)用,最常見的是使用MediaRecorderMediaPlayer來實現(xiàn)音頻的錄制和播放,更基礎(chǔ)點的會使用AudioRecordAudioTrack來實現(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)思路

此處應(yīng)有圖,一圖頂萬言
  1. 創(chuàng)建并初始化Audio Engine(音頻引擎,是和底層交互的入口)
  2. 打開OutputMix(音頻輸出),配置相關(guān)參數(shù)、Buffer Queue(緩沖隊列),以便進行音頻播放
  3. 打開Audio Input(音頻輸入),配置相關(guān)參數(shù),配置Buffer Queue,以便獲取音頻輸入
  4. 設(shè)置輸出、輸入的Callback(回調(diào)函數(shù)),實現(xiàn)將輸入傳給輸出的邏輯
  5. 啟動音頻錄制

也就是,這個程序?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的通用步驟是:

  1. 創(chuàng)建對象(通過帶有create的函數(shù))
  2. 初始化(通過Realize函數(shù))
  3. 獲取接口來使用相關(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也僅僅是草草接觸,如有不對或疏漏的地方,還請大家指正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容