Android OpenSL ES 開發:Android OpenSL 介紹和開發流程說明


一、Android OpenSL ES 介紹

OpenSL ES (Open Sound Library for Embedded Systems)是無授權費、跨平台、針對嵌入式系統精心優化的硬件音頻加速API。它為嵌入式移動多媒體設備上的本地應用程序開發者提供標准化, 高性能,低響應時間的音頻功能實現方法,並實現軟/硬件音頻性能的直接跨平台部署,降低執行難度,促進高級音頻市場的發展。簡單來說OpenSL ES是一個嵌入式跨平台免費的音頻處理庫。 

Android的OpenSL ES庫是在NDK的platforms文件夾對應android平台先相應cpu類型里面,如:

二、Android OpenSL ES 開發流程

OpenSL ES 的開發流程主要有如下6個步驟:

1、 創建接口對象

2、設置混音器

3、創建播放器(錄音器)

4、設置緩沖隊列和回調函數

5、設置播放狀態

6、啟動回調函數

注明:其中第4步和第6步是OpenSL ES 播放PCM等數據格式的音頻是需要用到的。

在使用OpenSL ES的API之前,需要引入OpenSL ES的頭文件,代碼如下:

#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>

由於是在Native層使用該特性,所需需要在Android.mk中增加鏈接選項,以便在鏈接階段使用到系統系統的OpenSL ES的so庫:

LOCAL_LDLIBS += -lOepnSLES

我們知道OpenSL ES提供的是基於C語言的API,但是它是基於對象和接口的方式提供的,會采用面向對象的思想開發API。因此我們先來了解一下OpenSL ES中對象和接口的概念:

  • 對象:對象是對一組資源及其狀態的抽象,每個對象都有一個在其創建時指定的類型,類型決定了對象可以執行的任務集,對象有點類似於C++中類的概念。
  • 接口:接口是對象提供的一組特征的抽象,這些抽象會為開發者提供一組方法以及每個接口的類型功能,在代碼中,接口的類型由接口ID來標識。

需要重點理解的是,一個對象在代碼中其實是沒有實際的表示形式的,可以通過接口來改變對象的狀態以及使用對象提供的功能。對象有可以有一個或者多個接口的實例,但是接口實例肯定只屬於一個對象。

如果明白了OpenSL ES 中對象和接口的概念,那么下面我們就繼續看看,在代碼中是如何使用它們的。

上面我們也提到過,對象是沒有實際的代碼表示形式的,對象的創建也是通過接口來完成的。通過獲取對象的方法來獲取出對象,進而可以訪問對象的其他的接口方法或者改變對象的狀態,下面是使用對象和接口的相關說明。

2.1 OpenSL ES 開發最重要的接口類 SLObjectItf

 通過SLObjectItf接口類我們可以創建所需要的各種類型的類接口,比如:

  • 創建引擎接口對象:SLObjectItf engineObject
  • 創建混音器接口對象:SLObjectItf outputMixObject
  • 創建播放器接口對象:SLObjectItf playerObject

以上等等都是通過SLObjectItf來創建的。

2.2 SLObjectItf 創建的具體的接口對象實例

OpenSL ES中也有具體的接口類,比如(引擎:SLEngineItf,播放器:SLPlayItf,聲音控制器:SLVolumeItf等等)。

2.3 創建引擎並實現

OpenSL ES中開始的第一步都是聲明SLObjectItf接口類型的引擎接口對象engineObject,然后用方法slCreateEngine創建一個引擎接口對象;創建好引擎接口對象后,需要用SLObjectItf的Realize方法來實現engineObject;最后用SLObjectItf的GetInterface方法來初始化SLEngnineItf對象實例。如:

SLObjectItf engineObject = NULL;//用SLObjectItf聲明引擎接口對象
SLEngineItf engineEngine = NULL;//聲明具體的引擎對象實例
 
void createEngine()
{
    SLresult result;//返回結果
    result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);//第一步創建引擎
    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);//實現(Realize)engineObject接口對象
    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);//通過engineObject的GetInterface方法初始化engineEngine
}

2.4 利用引擎對象創建其他接口對象

其他接口對象(SLObjectItf outputMixObject,SLObjectItf playerObject)等都是用引擎接口對象創建的(具體的接口對象需要的參數這里就說了,可參照ndk例子里面的),如:

//混音器
SLObjectItf outputMixObject = NULL;//用SLObjectItf創建混音器接口對象
SLEnvironmentalReverbItf outputMixEnvironmentalReverb = NULL;////創建具體的混音器對象實例
 
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, mids, mreq);//利用引擎接口對象創建混音器接口對象
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);//實現(Realize)混音器接口對象
result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB, &outputMixEnvironmentalReverb);//利用混音器接口對象初始化具體混音器實例
 
//播放器
SLObjectItf playerObject = NULL;//用SLObjectItf創建播放器接口對象
SLPlayItf playerPlay = NULL;//創建具體的播放器對象實例
 
result = (*engineEngine)->CreateAudioPlayer(engineEngine, &playerObject, &audioSrc, &audioSnk, 3, ids, req);//利用引擎接口對象創建播放器接口對象
result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);//實現(Realize)播放器接口對象
result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay);//初始化具體的播放器對象實例

最后就是使用創建好的具體對象實例來實現具體的功能。

三、OpenSL ES 使用示例

首先導入OpenSL ES和其他必須的庫:

-lOpenSLES -landroid 

3.1 播放assets文件

創建引擎——>創建混音器——>創建播放器——>設置播放狀態

JNIEXPORT void JNICALL
Java_com_renhui_openslaudio_MainActivity_playAudioByOpenSL_1assets(JNIEnv *env, jobject instance, jobject assetManager, jstring filename) {
 
    release();
    const char *utf8 = (*env)->GetStringUTFChars(env, filename, NULL);
 
    // use asset manager to open asset by filename
    AAssetManager* mgr = AAssetManager_fromJava(env, assetManager);
    AAsset* asset = AAssetManager_open(mgr, utf8, AASSET_MODE_UNKNOWN);
    (*env)->ReleaseStringUTFChars(env, filename, utf8);
 
    // open asset as file descriptor
    off_t start, length;
    int fd = AAsset_openFileDescriptor(asset, &start, &length);
    AAsset_close(asset);
 
    SLresult result;
 
 
    //第一步,創建引擎
    createEngine();
 
    //第二步,創建混音器
    const SLInterfaceID mids[1] = {SL_IID_ENVIRONMENTALREVERB};
    const SLboolean mreq[1] = {SL_BOOLEAN_FALSE};
    result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, mids, mreq);
    (void)result;
    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    (void)result;
    result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB, &outputMixEnvironmentalReverb);
    if (SL_RESULT_SUCCESS == result) {
        result = (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(outputMixEnvironmentalReverb, &reverbSettings);
        (void)result;
    }
    //第三步,設置播放器參數和創建播放器
    // 1、 配置 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};
 
    // 2、 配置 audio sink
    SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    SLDataSink audioSnk = {&loc_outmix, NULL};
 
    // 創建播放器
    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, &fdPlayerObject, &audioSrc, &audioSnk, 3, ids, req);
    (void)result;
 
    // 實現播放器
    result = (*fdPlayerObject)->Realize(fdPlayerObject, SL_BOOLEAN_FALSE);
    (void)result;
 
    // 得到播放器接口
    result = (*fdPlayerObject)->GetInterface(fdPlayerObject, SL_IID_PLAY, &fdPlayerPlay);
    (void)result;
 
    // 得到聲音控制接口
    result = (*fdPlayerObject)->GetInterface(fdPlayerObject, SL_IID_VOLUME, &fdPlayerVolume);
    (void)result;
 
    //第四步,設置播放狀態
    if (NULL != fdPlayerPlay) {
 
        result = (*fdPlayerPlay)->SetPlayState(fdPlayerPlay, SL_PLAYSTATE_PLAYING);
        (void)result;
    }
 
    //設置播放音量 (100 * -50:靜音 )
    (*fdPlayerVolume)->SetVolumeLevel(fdPlayerVolume, 20 * -50);
 
}

3.2 播放pcm文件

(集成到ffmpeg時,也是播放ffmpeg轉換成的pcm格式的數據),這里為了模擬是直接讀取的pcm格式的音頻文件。

1. 創建播放器和混音器

//第一步,創建引擎
    createEngine();
 
    //第二步,創建混音器
    const SLInterfaceID mids[1] = {SL_IID_ENVIRONMENTALREVERB};
    const SLboolean mreq[1] = {SL_BOOLEAN_FALSE};
    result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, mids, mreq);
    (void)result;
    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    (void)result;
    result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB, &outputMixEnvironmentalReverb);
    if (SL_RESULT_SUCCESS == result) {
        result = (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(
                outputMixEnvironmentalReverb, &reverbSettings);
        (void)result;
    }
    SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    SLDataSink audioSnk = {&outputMix, NULL};

2. 設置pcm格式的頻率位數等信息並創建播放器

// 第三步,配置PCM格式信息
    SLDataLocator_AndroidSimpleBufferQueue android_queue={SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,2};
    SLDataFormat_PCM pcm={
            SL_DATAFORMAT_PCM,//播放pcm格式的數據
            2,//2個聲道(立體聲)
            SL_SAMPLINGRATE_44_1,//44100hz的頻率
            SL_PCMSAMPLEFORMAT_FIXED_16,//位數 16位
            SL_PCMSAMPLEFORMAT_FIXED_16,//和位數一致就行
            SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,//立體聲(前左前右)
            SL_BYTEORDER_LITTLEENDIAN//結束標志
    };
    SLDataSource slDataSource = {&android_queue, &pcm};
 
 
    const SLInterfaceID ids[3] = {SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND, SL_IID_VOLUME};
    const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
    
    result = (*engineEngine)->CreateAudioPlayer(engineEngine, &pcmPlayerObject, &slDataSource, &audioSnk, 3, ids, req);
    //初始化播放器
    (*pcmPlayerObject)->Realize(pcmPlayerObject, SL_BOOLEAN_FALSE);
 
//    得到接口后調用  獲取Player接口
    (*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_PLAY, &pcmPlayerPlay);

3. 設置緩沖隊列和回調函數

//    注冊回調緩沖區 獲取緩沖隊列接口
    (*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_BUFFERQUEUE, &pcmBufferQueue);
    //緩沖接口回調
    (*pcmBufferQueue)->RegisterCallback(pcmBufferQueue, pcmBufferCallBack, NULL);

回調函數:

void * pcmBufferCallBack(SLAndroidBufferQueueItf bf, void * context)
{
    //assert(NULL == context);
    getPcmData(&buffer);
    // for streaming playback, replace this test by logic to find and fill the next buffer
    if (NULL != buffer) {
        SLresult result;
        // enqueue another buffer
        result = (*pcmBufferQueue)->Enqueue(pcmBufferQueue, buffer, 44100 * 2 * 2);
        // the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT,
        // which for this code example would indicate a programming error
    }
}

讀取pcm格式的文件:

void getPcmData(void **pcm)
{
    while(!feof(pcmFile))
    {
        fread(out_buffer, 44100 * 2 * 2, 1, pcmFile);
        if(out_buffer == NULL)
        {
            LOGI("%s", "read end");
            break;
        } else{
            LOGI("%s", "reading");
        }
        *pcm = out_buffer;
        break;
    }
}

4. 設置播放狀態並手動開始調用回調函數

//    獲取播放狀態接口
    (*pcmPlayerPlay)->SetPlayState(pcmPlayerPlay, SL_PLAYSTATE_PLAYING);
 
//    主動調用回調函數開始工作
    pcmBufferCallBack(pcmBufferQueue, NULL);

注意:

在回調函數中result = (*pcmBufferQueue)->Enqueue(pcmBufferQueue, buffer, 44100 * 2 * 2),最后的“44100*2*2”是buffer的大小,因為我這里是指定了沒讀取一次就從pcm文件中讀取了“44100*2*2”個字節,所以可以正常播放,如果是利用ffmpeg來獲取pcm數據源,那么實際大小要根據每個AVframe的具體大小來定,這樣才能正常播放出聲音!(44100 * 2 * 2 表示:44100是頻率HZ,2是立體聲雙通道,2是采用的16位采樣即2個字節,所以總的字節數就是:44100 * 2 * 2)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM