簡體   English   中英

OpenSL ES無需重新創建音頻播放器即可更改音頻源

[英]OpenSL ES change audio source without recreating audio player

我的布局有大約60個按鈕,每個按鈕在按下時都會播放不同的音頻文件。 我將所有音頻文件都保存為資產文件夾中的mp3,要播放它們,我基本上使用與Google NDK示例“本地音頻”項目中使用的相同的代碼: https : //github.com/googlesamples/android -ndk

我有10個相同的本機函數(僅帶有唯一命名的變量)就可以像這樣工作。

播放聲音的功能:

jboolean Java_com_example_nativeaudio_Fretboard_player7play(JNIEnv* env, jclass clazz, jobject assetManager, jstring filename)
{
    SLresult result;

    // convert Java string to UTF-8
    const char *utf8 = (*env)->GetStringUTFChars(env, filename, NULL);
    assert(NULL != utf8);
    // use asset manager to open asset by filename
    AAssetManager* mgr = AAssetManager_fromJava(env, assetManager);
    assert(NULL != mgr);
    AAsset* asset = AAssetManager_open(mgr, utf8, AASSET_MODE_UNKNOWN);
    // release the Java string and UTF-8
    (*env)->ReleaseStringUTFChars(env, filename, utf8);
    // the asset might not be found
    if (NULL == asset) {
        return JNI_FALSE;
    }
    // open asset as file descriptor
    off_t start, length;
    int fd = AAsset_openFileDescriptor(asset, &start, &length);
    assert(0 <= fd);
    AAsset_close(asset);

    // configure audio source
    SLDataLocator_AndroidFD loc_fd = {SL_DATALOCATOR_ANDROIDFD, fd, start, length};
    SLDataFormat_MIME format_mime = {SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_UNSPECIFIED};
    SLDataSource audioSrc = {&loc_fd, &format_mime};
    // configure audio sink
    SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    SLDataSink audioSnk = {&loc_outmix, NULL};
    // create audio player
    const SLInterfaceID ids[3] = {SL_IID_SEEK, SL_IID_MUTESOLO, SL_IID_VOLUME};
    const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
    result = (*engineEngine)->CreateAudioPlayer(engineEngine, &p7PlayerObject, &audioSrc, &audioSnk,
                                                3, ids, req);
    assert(SL_RESULT_SUCCESS == result);
    (void)result;
    // realize the player
    result = (*p7PlayerObject)->Realize(p7PlayerObject, SL_BOOLEAN_FALSE);
    assert(SL_RESULT_SUCCESS == result);
    (void)result;
    // get the play interface
    result = (*p7PlayerObject)->GetInterface(p7PlayerObject, SL_IID_PLAY, &p7PlayerPlay);
    assert(SL_RESULT_SUCCESS == result);
    (void)result;

    if (NULL != p7PlayerPlay) {
        // play
        result = (*p7PlayerPlay)->SetPlayState(p7PlayerPlay, SL_PLAYSTATE_PLAYING);
        assert(SL_RESULT_SUCCESS == result);
        (void)result;
    }

    return JNI_TRUE;
}

停止聲音的功能:

void Java_com_example_nativeaudio_Fretboard_player7stop(JNIEnv* env, jclass clazz)
{
    SLresult result;

    // make sure the asset audio player was created
    if (NULL != p7PlayerPlay) {
        // set the player's state
        result = (*p7PlayerPlay)->SetPlayState(p7PlayerPlay, SL_PLAYSTATE_STOPPED);
        assert(SL_RESULT_SUCCESS == result);
        (void)result;
        // destroy file descriptor audio player object, and invalidate all associated interfaces
        (*p7PlayerObject)->Destroy(p7PlayerObject);
        p7PlayerObject = NULL;
        p7PlayerPlay = NULL;
    }
}

這很容易處理,但是我想最小化延遲並避免每次我想播放不同文件時都必須做(*engineEngine)->CreateAudioPlayer() 有什么方法可以只更改音頻播放器使用的audioSrc,而不必每次都從頭開始銷毀它並重新創建它?

作為獎勵,我在哪里可以閱讀有關該材料的更多信息? 似乎很難在任何地方找到有關OpenSL ES的任何信息。

我們在同一條船上,我目前也在使自己熟悉NDK和OpenSL ES。 我的答案是基於我的經驗,該經驗完全由大約2天的實驗組成,因此可能會有更好的方法,但是這些信息可能會對您有所幫助。

我有10個相同的本機函數(僅帶有唯一命名的變量)就可以像這樣工作。

如果我正確理解了您的情況,則無需為此重復功能。 這些調用中唯一不同的是按下的按鈕以及最終播放的聲音,並且可以將其作為參數通過JNI調用進行傳遞。 您可以將創建的播放器和數據存儲在全局可訪問的結構中,以便在需要停止/重放它時可以將其檢索,也許可以使用buttonId作為地圖的鍵。

[..]但是我想最小化延遲並避免每次我想播放不同文件時都必須做(* engineEngine)-> CreateAudioPlayer()。 有什么方法可以只更改音頻播放器使用的audioSrc,而不必每次都從頭開始銷毀它並重新創建它?

是的,不斷創建和銷毀播放器成本很高,並且可能導致堆碎片化(如OpenSL ES 1.0規范中所述)。 首先,我以為他可以使用DynamicSourceItf切換數據源,但似乎該接口不打算那樣使用,至少在Android 6上,此接口返回“功能不受支持”。

我懷疑為每種獨特的聲音創建一個播放器是否會是一個好的解決方案,尤其是因為多次反復播放相同的聲音(例如在游戲中很常見)會需要任意數量的其他播放器來播放相同的聲音。

緩沖區隊列

BufferQueues是玩家在播放時要處理的各個緩沖區的隊列。 處理完所有緩沖區后,播放器會“停止”(盡管它的官方狀態仍在“播放”中),但是一旦新緩沖區入隊,它將立即恢復。

這允許您做的是根據需要的重疊聲音創建盡可能多的播放器。 當您想播放聲音時,請遍歷這些播放器,直到找到一個當前不正在處理緩沖區的播放器為止( BufferQueueItf->GetState(...)提供了此信息,或者可以注冊一個回調,以便可以將播放器標記為是“免費的”)。 然后,根據您的聲音需要加入盡可能多的緩沖區,這些緩沖區將立即開始播放。

據我所知,BufferQueue的格式在創建時就被鎖定了。 因此,您必須確保所有輸入緩沖區都具有相同的格式,或者為每種格式創建不同的BufferQueue(和播放器)。

Android簡單BufferQueue

根據Android NDK文檔,預計BufferQueue接口將來會發生重大變化。 他們提取了具有大多數BufferQueue功能的簡化接口,並將其稱為AndroidSimpleBufferQueue。 預計該接口不會更改,因此可以使您的代碼更富未來性。

通過使用AndroidSimpleBufferQueue釋放的主要功能是能夠使用非PCM源數據,因此您必須在使用之前解碼文件。 這可以在OpenSL ES中使用AndroidSimpleBufferQueue作為接收器來完成。 最新的API使用MediaCodec及其NDK實現NDKMedia(請參見本機編解碼器示例)具有更多支持。

資源

NDK文檔確實包含一些重要的信息,這些信息很難在其他任何地方找到。 這是 OpenSL ES的特定頁面。

它可能接近600頁,難以理解,但是OpenSL ES 1.0規范應該是您的主要信息資源。 我強烈建議您閱讀第4章,因為它很好地概述了工作原理。 第3章提供了有關特定設計的更多信息。 然后,我隨即使用搜索功能跳來跳去閱讀接口和對象。

了解OpenSL ES

一旦您了解了OpenSL如何工作的基本原理,這似乎很簡單。 有媒體對象(播放器和記錄器等)以及數據源(輸入)和數據接收器(輸出)。 本質上,您將輸入連接到媒體對象,該對象將處理后的數據路由到其連接的輸出。

規范中記錄了源,接收器和媒體對象,包括它們的接口。 有了這些信息,實際上就是選擇所需的構建基塊並將其插入在一起。

更新16年7月29日

根據我的測試,似乎BufferQueue和AndroidSimpleBufferQueue都不支持非PCM數據,至少在我測試過的系統(Nexus 7 @ 6.01,NVidia Shield K1 @ 6.0.1)上不支持,因此您需要解碼您的數據,然后才能使用。

我嘗試使用MediaExtractor和MediaCodec的NDK版本,但需要注意以下幾點:

  • MediaExtractor似乎無法正確返回使用加密進行解碼所需的UUID信息,至少不是針對我測試過的文件。 AMediaExtractor_getPsshInfo返回nullptr

  • API並不總是像標頭聲明中的注釋一樣起作用。 例如,通過檢查返回的字節數而不是檢查AMediaExtractor_advance函數的返回值,在MediaExtractor中檢查EOS(流的末尾)似乎是最可靠的。

我建議您繼續使用Java進行解碼,因為這些API更加成熟,經過了嚴格的測試,並且您可能會從中獲得更多的功能。 一旦有了原始PCM數據的緩沖區,就可以將其傳遞給本機代碼,從而減少延遲。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM