Android Open SL ES — 官方Demo解析native-audio

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展。

主要功能介紹

  1. Assets目錄音頻播放
  2. c頭文件形式播放
  3. 本地音頻播放
  4. 錄音和回放

本文中主要介紹從緩沖隊(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)建過程都是一樣的步驟。

  1. create
  2. Realize
  3. 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末哼鬓,一起剝皮案震驚了整個(gè)濱河市监右,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌异希,老刑警劉巖健盒,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異称簿,居然都是意外死亡扣癣,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門憨降,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)父虑,“玉大人,你說(shuō)我怎么就攤上這事券册∑到危” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵烁焙,是天一觀的道長(zhǎng)航邢。 經(jīng)常有香客問我,道長(zhǎng)骄蝇,這世上最難降的妖魔是什么膳殷? 我笑而不...
    開封第一講書人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上赚窃,老公的妹妹穿的比我還像新娘册招。我一直安慰自己,他們只是感情好勒极,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開白布是掰。 她就那樣靜靜地躺著,像睡著了一般辱匿。 火紅的嫁衣襯著肌膚如雪键痛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評(píng)論 1 304
  • 那天匾七,我揣著相機(jī)與錄音絮短,去河邊找鬼。 笑死昨忆,一個(gè)胖子當(dāng)著我的面吹牛丁频,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播邑贴,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼席里,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了拢驾?” 一聲冷哼從身側(cè)響起胁勺,我...
    開封第一講書人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎独旷,沒想到半個(gè)月后署穗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嵌洼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年案疲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片麻养。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡褐啡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鳖昌,到底是詐尸還是另有隱情备畦,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布许昨,位于F島的核電站懂盐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏糕档。R本人自食惡果不足惜莉恼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧俐银,春花似錦尿背、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至吱七,卻和暖如春坞淮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背陪捷。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诺擅,地道東北人市袖。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像烁涌,于是被迫代替她去往敵國(guó)和親苍碟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容