Open SL ES簡(jiǎn)介
OpenSL ES - 嵌入式音頻加速標(biāo)準(zhǔn)戳玫。OpenSL ES? 是無(wú)授權(quán)費(fèi)、跨平臺(tái)未斑、針對(duì)嵌入式系統(tǒng)精心優(yōu)化的硬件音頻加速API咕宿。它為嵌入式移動(dòng)多媒體設(shè)備上的本地應(yīng)用程序開發(fā)者提供標(biāo)準(zhǔn)化, 高性能,低響應(yīng)時(shí)間的音頻功能實(shí)現(xiàn)方法,并實(shí)現(xiàn)軟/硬件音頻性能的直接跨平臺(tái)部署蜡秽,降低執(zhí)行難度府阀,促進(jìn)高級(jí)音頻市場(chǎng)的發(fā)展。在Android中主要用到了一部分Open SL ES的功能芽突,兩者之間有交集但不完全一樣试浙,Android有自己的一部分?jǐn)U展。
主要功能介紹
- Assets目錄音頻播放
- c頭文件形式播放
- 本地音頻播放
- 錄音和回放
本文中主要介紹從緩沖隊(duì)列中播放和錄音到緩沖隊(duì)列中寞蚌,主要用到以下幾個(gè)native方法田巴。其他方法操作步驟類似,可以參考學(xué)習(xí)挟秤。
代碼解析
public static native void createEngine();
public static native void createBufferQueueAudioPlayer(int sampleRate, int samplesPerBuf);
public static native boolean createAudioRecorder();
public static native void startRecording();
public static native void shutdown();
從native方法的排序可以看出程序的調(diào)用流程
從圖中可以看出不管是播放還是錄音都必須先創(chuàng)建引擎壹哺,在Open SL ES中所有的對(duì)象創(chuàng)建過程都是一樣的步驟。
- create
- Realize
- GetInterface
一艘刚、Engine的創(chuàng)建基本就是這幾個(gè)步驟,Interface 可以根據(jù)自己的業(yè)務(wù)需求來(lái)獲取管宵,不需要的可以刪除。
// 創(chuàng)建引擎
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
// 獲取引擎接口
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineItf);
// create output mix, with environmental reverb specified as a non-required interface
// const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB};
// SLboolean req[1] = {SL_BOOLEAN_FALSE};
result = (*engineItf)->CreateOutputMix(engineItf, &outputMixObject, 0, NULL, NULL);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// realize the output mix
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
二昔脯、 createBufferQueueAudioPlayer
創(chuàng)建的步驟和引擎類似啄糙,從上面的圖片中可以出,多了一些東西需要配置云稚,DataSource 和 DataSink隧饼,DataSource 顧名思義就是要播放的數(shù)據(jù)源,緩沖隊(duì)列的播放形式只支持PCM格式的數(shù)據(jù)源静陈,DataSink 就是音頻輸出燕雁,也就是創(chuàng)建引擎時(shí)獲取的 outputMixObject 對(duì)象。PCM 數(shù)據(jù)源需要配置采樣率鲸拥、通道拐格、定點(diǎn)、揚(yáng)聲器刑赶、小端模式捏浊,參數(shù)配置需要參考官方文檔,有些參數(shù)是不支持的撞叨,需要測(cè)試金踪。另一個(gè)比較重要的就是緩沖隊(duì)列和播放回調(diào)浊洞,因?yàn)楹弯浺粢粯樱苑诺较旅嬉黄鸾榻B胡岔。
// create BufferQueueAudioPlayer
// configure audio source
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, SL_BUFFERSIZE};
SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM,
1,
SL_SAMPLINGRATE_32,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_SPEAKER_FRONT_CENTER,
SL_BYTEORDER_LITTLEENDIAN}; //合適pcm 單通道 采樣率 定點(diǎn) 揚(yáng)聲器 小端
// if (sampleRate) {
// format_pcm.samplesPerSec = (SLuint32) sampleRate;
// }
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
SLDataSink audioSnk = {&loc_outmix, NULL};
//需要請(qǐng)求的接口 緩沖隊(duì)列 音量
const SLInterfaceID idsAudioPlayer[3] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
const SLboolean reqAudioPlayer[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
result = (*engineItf)->CreateAudioPlayer(engineItf, &playerObject, &audioSrc, &audioSnk, 2,idsAudioPlayer, reqAudioPlayer);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// realize the player
result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerItf);
result = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &playerBufferQueueItf);
result = (*playerBufferQueueItf)->RegisterCallback(playerBufferQueueItf, playCallback, audioProcess);
//靜音模式/solo不支持單聲道
// mute/solo is not supported for sources that are known to be mono, as this is
// get the mute/solo interface
//result = (*playerObject)->GetInterface(playerObject, SL_IID_MUTESOLO, &playerMuteSoloItf);
// get the volume interface
result = (*playerObject)->GetInterface(playerObject, SL_IID_VOLUME, &playerVolumeItf);
三 法希、createAudioRecorder
錄音的創(chuàng)建和播放類似,只是它們的 DataSource 和 DataSink 不同靶瘸,錄音的數(shù)據(jù)源 DataSource 是來(lái)自麥克風(fēng)苫亦,輸出數(shù)據(jù)DataSink 和 播放的數(shù)據(jù)源是一樣的,PCM格式的數(shù)據(jù)怨咪,配置方式也是一樣屋剑。
//錄音
// configure audio source
SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};
SLDataSource recordAudioSrc = {&loc_dev, NULL};
// configure audio sink
SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, SL_BUFFERSIZE};//緩沖的數(shù)量
SLDataFormat_PCM recordFormat_pcm = {SL_DATAFORMAT_PCM,
1,
SL_SAMPLINGRATE_32,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_SPEAKER_FRONT_CENTER,
SL_BYTEORDER_LITTLEENDIAN};
// if (sampleRate) {
// recordFormat_pcm.samplesPerSec = (SLuint32) sampleRate;
// }
SLDataSink recordSink = {&loc_bq, &recordFormat_pcm};
const SLInterfaceID idsRecord[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
const SLboolean reqRecord[1] = {SL_BOOLEAN_TRUE};
//創(chuàng)建錄音對(duì)象
result = (*engineItf)->CreateAudioRecorder(engineItf,
&recorderObject,
&recordAudioSrc,
&recordSink,
1,
idsRecord,
reqRecord);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// realize the audio recorder
result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// 獲取錄音接口
result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderItf);
result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&recorderBufferQueueItf);
result = (*recorderBufferQueueItf)->RegisterCallback(recorderBufferQueueItf, recordCallback,
audioProcess);
四、播放和錄音的緩沖隊(duì)列機(jī)制
在創(chuàng)建播放和錄音的時(shí)候都設(shè)置了緩沖隊(duì)列和回調(diào)惊暴,注意播放和錄音代碼中都配置了一個(gè) SL_BUFFERSIZE 參數(shù)饼丘,這是我自定義的一個(gè)宏,根據(jù)名字可以推測(cè)這是設(shè)置緩沖隊(duì)列的大小辽话,也就是當(dāng)前 BufferQueue 中最大可以容納的buffer數(shù)量肄鸽。 每次播完或錄完一個(gè) buffer 都會(huì)回調(diào)創(chuàng)建時(shí) RegisterCallback 傳入的callback方法,代表當(dāng)前buffer數(shù)據(jù)已經(jīng)處理完畢油啤,buffer出隊(duì)典徘, 如果是錄音你應(yīng)該獲取這個(gè) buffer 保存下來(lái),寫文件或者其他操作都可以益咬;如果是播放就代表 當(dāng)前buffer已經(jīng)播放完畢逮诲,你可以 Enqueue 下一個(gè)buffer了。Enqueue到隊(duì)列中的buffer數(shù)量超過了你設(shè)置的 SL_BUFFERSIZE 值幽告,會(huì)報(bào)錯(cuò)SL_RESULT_BUFFER_INSUFFICIENT梅鹦。
五、需要注意的坑
當(dāng)你需要停止錄音和停止播放的時(shí)候冗锁,調(diào)用以下代碼齐唆,官方案例也是這么做的,但是這么做會(huì)有bug冻河,當(dāng)你下次再開始錄音或播放的時(shí)候箍邮,會(huì)報(bào)錯(cuò)SL_RESULT_BUFFER_INSUFFICIENT,也就是說(shuō)隊(duì)列滿了叨叙。根據(jù)測(cè)試發(fā)現(xiàn)Clear函數(shù)并不起作用锭弊。因?yàn)樵O(shè)置了SL_PLAYSTATE_STOPPED狀態(tài),錄音或播放已經(jīng)停止了擂错,所以不會(huì)再回調(diào)callback函數(shù)了味滞,也就是代表剩余的buffer沒用出隊(duì),所以會(huì)報(bào)錯(cuò)。
//停止播放
SLresult result = (*playerItf)->SetPlayState(playerItf, SL_PLAYSTATE_STOPPED);
assert(SL_RESULT_SUCCESS == result);
(void) result;
result = (*playerBufferQueueItf)->Clear(playerBufferQueueItf);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// 停止錄音
result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED);
assert(SL_RESULT_SUCCESS == result);
(void)result;
result = (*recorderBufferQueue)->Clear(recorderBufferQueue);
assert(SL_RESULT_SUCCESS == result);
(void)result;
解決辦法:
思路一:
在callback中判斷緩沖隊(duì)列的狀態(tài)剑鞍,等到buffer全部出隊(duì)再停止播放或錄音刹悴,這樣就會(huì)有延遲,延遲時(shí)間是根據(jù)你設(shè)置的 SL_BUFFERSIZE 隊(duì)列大小來(lái)決定的攒暇,所以 SL_BUFFERSIZE 不宜設(shè)置太大,官方DEMO推薦至少設(shè)置2個(gè)子房,buffer數(shù)組也不宜過大形用,假設(shè)采樣率是32000,buffer是 short *buffer[512] 延遲是16毫秒证杭,2個(gè)就是32毫秒田度,這樣的延遲幾乎可以忽略了。
AudioProcess *audioProcess = context;
SLAndroidSimpleBufferQueueState recQueueState;//緩沖區(qū)狀態(tài)
(*bq)->GetState(bq, &recQueueState);
if (recQueueState.count == 0) {
SLresult result = (*audioProcess->playerItf)->SetPlayState(audioProcess->playerItf, SL_PLAYSTATE_STOPPED);
assert(SL_RESULT_SUCCESS == result);
(void) result;
result = (*audioProcess->playerBufferQueueItf)->Clear(audioProcess->playerBufferQueueItf);
assert(SL_RESULT_SUCCESS == result);
(void) result;
LOGD("停止播放");
}
思路二:
下次開始錄制或播放的時(shí)候重置buffer數(shù)據(jù)解愤,接著走回調(diào)函數(shù)镇饺,直接在callback中控制流程,這樣就相當(dāng)于 SL_PLAYSTATE_STOPPED 是一個(gè)暫停的狀態(tài)送讲,可以避免播放和錄音的啟動(dòng)時(shí)間奸笤,也是一種不錯(cuò)的方案。
參考資料:
官方DEMO
音頻相關(guān)博客推薦
Open SL ES 資料:ndk目錄/docs/Additional_library_docs/opensles/OpenSL_ES_Specification_1.0.1.pdf