1. 程式人生 > >Android OpenSL ES 開發:Android OpenSL 介紹和開發流程說明

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

ror logic ogr activity engine eid 優化 als 分享

一、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是播放PCM等數據格式的音頻是需要用到的。

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)

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