天天看點

34.FFmpeg+OpenGLES+OpenSLES播放器實作(八.OpenSLES播放音頻)

項目源碼 OpenSL ES 文檔
OpenSLES:(Open Sound Library for Embedded Systems)

OpenSLES是跨平台、針對嵌入式系統精心優化的硬體音頻加速API。使用OpenSLES進行音頻播放的好處是可以不依賴第三方。它為嵌入式移動多媒體裝置上的本地應用程式開發者提供标準化, 高性能,低響應時間的音頻功能實作方法,并實作軟/硬體音頻性能的直接跨平台部署,降低執行難度。

今天我們就通過OpenSLES代碼來實作播放pcm格式的音頻檔案,後期會将相關代碼整合到ffmpeg播放器上,實作音視訊同步。下邊是OpenSLES播放音頻的簡要流程(圖檔來自網上)

34.FFmpeg+OpenGLES+OpenSLES播放器實作(八.OpenSLES播放音頻)

OpenSLES音頻播放流程.png

接下來進入代碼階段

首先使用OpenSLES需要将這個庫連結到我們的so中,在CMakeLists.txt腳本中添加

target_link_libraries( 
                       ......
                       #播放音頻
                       OpenSLES )
           
實作流程

1.建立引擎

slCreateEngine(),第一個參數是要建立的引擎對象,是一個SLObjectItf類型,Object是一個資源的抽象集合,可以通過它擷取各種資源,所有的Object在OpenSL裡面我們拿到的都是一個SLObjectItf,傳回值是SLresult類型,如果成功則傳回SL_RESULT_SUCCESS,其他參數都傳0即可,我們務求最精簡的實作功能

SLObjectItf             *pEngine,
SLuint32                numOptions,
const SLEngineOption    *pEngineOptions,
SLuint32                numInterfaces,
const SLInterfaceID     *pInterfaceIds,
const SLboolean         * pInterfaceRequired
           

2.引擎對象建立後執行個體化,建立出來之後必須先調用Realize方法做初始化。在不需要使用的時候調用Destroy方法釋放資源

(*slObjectItf)->Realize(),執行個體化成功則傳回SL_RESULT_SUCCESS

SLObjectItf self, //要被執行個體化的引擎對象本身
SLboolean async //傳SL_BOOLEAN_FALSE
           

3.引擎執行個體化之後從引擎對象擷取接口

Interface則是方法的集合,例如SLRecordItf裡面包含了和錄音相關的方法,SLPlayItf包含了和播放相關的方法。我們功能都是通過調用Interfaces的方法去實作的,個Object裡面可能包含了多個Interface,是以GetInterface方法有個SLInterfaceID參數來指定到的需要擷取Object裡面的那個Interface。調用這個方法會擷取到SLEngineItf,SLEngineItf是OpenSL裡面最重要的一個Interface,我們可以通過它去建立各種Object,例如播放器、錄音器、混音器的Object,然後在用這些Object去擷取各種Interface去實作各種功能

(*slObjectItf)->GetInterface() 執行會後會擷取到SLEngineItf slEngineItf對象

SLObjectItf self,  //執行個體化後的引擎對象
const SLInterfaceID iid, //SL_IID_ENGINE
void * pInterface //輸出的接口對象指針
           

4.建立混音器

混音器是從建立出的引擎接口中建立的

(*slEngineItf)->CreateOutputMix(),輸出的混音器同樣是SLObjectItf mixObjectItf類型

SLEngineItf self,  //引擎接口
SLObjectItf * pMix, //輸出的混音器
SLuint32 numInterfaces,//傳0
const SLInterfaceID * pInterfaceIds,//傳0
const SLboolean * pInterfaceRequired //傳0
           

5.混音器對象建立後開始執行個體化,同引擎執行個體化

(*mixObjectItf)->Realize()

SLObjectItf self, //要被執行個體化的混音器對象本身
SLboolean async //傳SL_BOOLEAN_FALSE
           

6.建立播放器

(*slEngineItf)->CreateAudioPlayer(),播放器是從引擎對象建立出來的

SLEngineItf self, //引擎對象本身
SLObjectItf * pPlayer, //輸出的播放器對象,同樣是SLObjectItf類型
SLDataSource *pAudioSrc,//資料的來源
SLDataSink *pAudioSnk, //資料的去處,和SLDataSource是相對的
SLuint32 numInterfaces,//與下面的SLInterfaceID和SLboolean配合使用,用于标記SLInterfaceID數組和SLboolean的大小
const SLInterfaceID * pInterfaceIds, //這裡需要傳入一個數組,指定建立的播放器會包含哪些Interface
const SLboolean * pInterfaceRequired  //這裡也是一個數組,用來标記每個需要包含的Interface
           

7.初始化播放器

(*slPlayerItf)->Realize() 得到SLObjectItf slPlayerItf = NULL;

SLObjectItf self, //要被執行個體化的播放器對象本身
SLboolean async //傳SL_BOOLEAN_FALSE
           

8.擷取播放器接口

(*slPlayerItf)->GetInterface(slPlayerItf, SL_IID_PLAY, &slPlayItf);

得到播放器接口SLPlayItf slPlayItf

SLObjectItf self,  //執行個體化後的播放器對象
const SLInterfaceID iid, //SL_IID_ENGINE
void * pInterface //輸出的接口對象指針
           

9,擷取播放隊列接口

(*slPlayerItf)->GetInterface(slPlayerItf, SL_IID_BUFFERQUEUE, &pcmQueue)

SLObjectItf self, 
const SLInterfaceID iid, //SL_IID_ENGINE
void * pInterface //輸出的接口對象指針
           

10.給播放隊列設定回調函數

(*pcmQueue)->RegisterCallback(pcmQueue, CallBack, 0);

開始播放後會不斷的回調這個函數将音頻資料壓入隊列

void CallBack(SLAndroidSimpleBufferQueueItf bf, void *contex) {
    LOGI("CallBack ....");
    static FILE *fp = NULL;
    static char *buf = NULL;
    if (!buf) {
        buf = new char[1024 * 1024];
    }
    if (!fp) {
        fp = fopen("/sdcard/test.pcm", "rb");
    }
    if (!fp)return;
    if (feof(fp) == 0) {
        int len = fread(buf, 1, 1024, fp);
        if (len > 0)
            (*bf)->Enqueue(bf, buf, len);
    }
}
           

11.設定播放狀态為播放中

(*slPlayItf)->SetPlayState(slPlayItf, SL_PLAYSTATE_PLAYING);
           

12.開始播放

(*pcmQueue)->Enqueue(pcmQueue, "", 1);
           
34.FFmpeg+OpenGLES+OpenSLES播放器實作(八.OpenSLES播放音頻)

播放流程.png

完整代碼
void CallBack(SLAndroidSimpleBufferQueueItf bf, void *contex) {
    LOGI("CallBack ....");
    static FILE *fp = NULL;
    static char *buf = NULL;
    if (!buf) {
        buf = new char[1024 * 1024];
    }
    if (!fp) {
        fp = fopen("/sdcard/test.pcm", "rb");
    }
    if (!fp)return;
    if (feof(fp) == 0) {
        int len = fread(buf, 1, 1024, fp);
        if (len > 0)
            (*bf)->Enqueue(bf, buf, len);
    }
}

/**
 * 播放音頻
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_rzm_ffmpegplayer_FFmpegPlayer_playAudio(JNIEnv *env, jobject instance, jstring url_) {
    const char *url = env->GetStringUTFChars(url_, 0);

    /***********  1 建立引擎 擷取SLEngineItf***************/
    SLObjectItf slObjectItf;
    SLEngineItf slEngineItf;
    //SLresult是unsigned int 類型
    SLresult result;
    result = slCreateEngine(&slObjectItf, 0, 0, 0, 0, 0);
    if (result != SL_RESULT_SUCCESS)
        return;
    //SLObjectItf本身是一個指針,*slObjectItf得到的是他的對象
    result = (*slObjectItf)->Realize(slObjectItf, SL_BOOLEAN_FALSE);
    if (result != SL_RESULT_SUCCESS)
        return;
    result = (*slObjectItf)->GetInterface(slObjectItf, SL_IID_ENGINE, &slEngineItf);
    if (result != SL_RESULT_SUCCESS)
        return;
    if (slEngineItf) {
        LOGI("get SLEngineItf success");
    } else {
        LOGI("get SLEngineItf failed");
    }
    /***********         1 建立引擎       ***************/

    /***********  2 建立混音器 ***************/
    SLObjectItf mixObjectItf = NULL;
    result = (*slEngineItf)->CreateOutputMix(slEngineItf, &mixObjectItf, 0, 0, 0);
    if (result != SL_RESULT_SUCCESS) {
        LOGE("CreateOutputMix failed");
        return;
    } else {
        LOGE("CreateOutputMix success");
    }

    //執行個體化混音器
    result = (*mixObjectItf)->Realize(mixObjectItf, SL_BOOLEAN_FALSE);
    if (result != SL_RESULT_SUCCESS) {
        LOGE("mixer init failed");
    } else {
        LOGI("mixer init success");
    }
    /***********  2 建立混音器 ***************/

    /***********  3 配置音頻資訊 ***************/

    SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, mixObjectItf};
    SLDataSink slDataSink = {&outputMix, 0};


    //緩沖隊列
    SLDataLocator_AndroidSimpleBufferQueue queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 10};
    //音頻格式
    SLDataFormat_PCM pcmFormat = {
            SL_DATAFORMAT_PCM,
            //聲道數
            2,
            SL_SAMPLINGRATE_44_1,
            SL_PCMSAMPLEFORMAT_FIXED_16,
            SL_PCMSAMPLEFORMAT_FIXED_16,
            SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
            //位元組序,小端
            SL_BYTEORDER_LITTLEENDIAN
    };
    SLDataSource slDataSource = {&queue, &pcmFormat};
    /***********  3 配置音頻資訊 ***************/

    /************* 4 建立播放器 ****************/
    const SLInterfaceID ids[] = {SL_IID_BUFFERQUEUE};
    const SLboolean req[] = {SL_BOOLEAN_TRUE};
    SLObjectItf slPlayerItf = NULL;
    SLPlayItf slPlayItf;
    SLAndroidSimpleBufferQueueItf pcmQueue = NULL;
    result = (*slEngineItf)->CreateAudioPlayer(slEngineItf, &slPlayerItf, &slDataSource,
                                               &slDataSink, sizeof(ids) / sizeof(SLInterfaceID),
                                               ids, req);
    if (result != SL_RESULT_SUCCESS) {
        LOGE("create audio player failed");
    } else {
        LOGE("create audio player success");
    }
    //初始化播放器
    result = (*slPlayerItf)->Realize(slPlayerItf, SL_BOOLEAN_FALSE);
    if (result != SL_RESULT_SUCCESS) {
        LOGE("audio player init failed");
    } else {
        LOGE("audio player init success");
    }
    //擷取player接口
    result = (*slPlayerItf)->GetInterface(slPlayerItf, SL_IID_PLAY, &slPlayItf);
    if (result != SL_RESULT_SUCCESS) {
        LOGE("player get SL_IID_PLAY failed");
    } else {
        LOGI("player get SL_IID_PLAY success");
    }

    //擷取播放隊列接口
    result = (*slPlayerItf)->GetInterface(slPlayerItf, SL_IID_BUFFERQUEUE, &pcmQueue);
    if (result != SL_RESULT_SUCCESS) {
        LOGE("player get SL_IID_BUFFERQUEUE failed");
    } else {
        LOGI("player get SL_IID_BUFFERQUEUE success");
    }
    /************* 4 建立播放器 ****************/

    //設定回調函數
    (*pcmQueue)->RegisterCallback(pcmQueue, CallBack, 0);
    //設定播放狀态
    (*slPlayItf)->SetPlayState(slPlayItf, SL_PLAYSTATE_PLAYING);
    //啟動隊列
    (*pcmQueue)->Enqueue(pcmQueue, "", 1);

    env->ReleaseStringUTFChars(url_, url);
}

           

繼續閱讀