android平臺OpenSL ES播放PCM數(shù)據(jù)

目錄

  1. OpenSL ES是什么?
  2. 主要功能
  3. Android 平臺的OpenSL ES
  4. 使用OpenSL ES 的優(yōu)點
  5. API簡要介紹
  6. 示例
  7. 參考

1. OpenSL ES是什么?

OpenSL ES(Open Sound Library for Embedded Systems,開源的嵌入式聲音庫)是一個免授權費、跨平臺、C語言編寫的適用于嵌入式系統(tǒng)的硬件加速音頻庫。它為移動和游戲行業(yè)的開發(fā)者提供標準化、高性能、低延遲的方法來實現(xiàn)音頻功能,并致力于跨多個平臺輕松移植應用程序。OpenSL ES由非營利性技術聯(lián)盟Khronos Group管理。

OpenSL ES的設計目標是讓應用程序開發(fā)人員能夠訪問高級音頻功能,如3D定位音頻和MIDI播放,同時努力在制造商和平臺之間輕松實現(xiàn)應用程序移植。

簡要來說 OpenSL ES 是一套針對嵌入式平臺的音頻功能API標準。

2. 主要功能

OpenSL ES主要功能包括:

  • 基本音頻播放和錄制。
  • 3D音頻效果,包括3D定位音頻。
  • 音樂體驗增強效果,包括低音增強和環(huán)境混響。
  • 緩沖隊列。

3. Android 平臺的OpenSL ES

Android 2.3將OpenSL ES 1.0.1作為其NDK的一部分。在之后的版本中,實現(xiàn)的延遲有所改進。

Android 實現(xiàn)的 OpenSL ES 只是 OpenSL ES 1.0.1 的子集,并且進行了擴展。因此,對于 OpenSL ES API 的使用,我們需要特別留意哪些是 Android 支持的,哪些是不支持的。


image

不支持的功能:

  • 不支持 MIDI。
  • 不支持直接播放 DRM 或者 加密的內容。
  • 不支持音頻數(shù)據(jù)的編解碼,如需編解碼,需要使用 MediaCodec API 或者第三方庫。
  • 在音頻延時方面,相比于JAVA的 API,并沒有特別明顯地改進。

4. 使用OpenSL ES 的優(yōu)點

  • 相比于 Java API,避免音頻數(shù)據(jù)頻繁在 native 層和 Java 層拷貝,提高效率。
  • 相比于 Java API,可以更靈活地控制參數(shù)。
  • 使用 C 代碼,可以做深度優(yōu)化,比如采用 NEON 優(yōu)化。

5. API簡要介紹

OpenSL ES 雖然是 C 語言編寫,但是它的接口采用的是面向對象的方式,并不是提供一系列的函數(shù)接口,而是以 Interface 的方式來提供 API,這是理解 OpenSL ES API 的一個比較重要的點。

它的大都數(shù) API 需要這樣訪問:

//下面代碼是對 Audio Engine 對象進行 “初始化”
SLEngineItf engineObject;
SLresult result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);

如果在 Android NDK 下開發(fā)過 C 代碼,就應該不會太陌生,因為我們調用 “JNI* env” 的函數(shù)也是這個樣子去調用的。

5.1 Object 和 Interface

OpenSL ES 有兩個重要的概念 Object 和 Interface,“對象”和“接口”。
(1) 每個 Object 可能會存在一個或者多個 Interface,官方為每一種 Object 都定義了一系列的 Interface。
(2) 每個 Object 對象都提供了一些最基礎的操作,比如:Realize,Resume,GetState,Destroy 等等,如果希望使用該對象支持的功能函數(shù),則必須通過其 GetInterface 函數(shù)拿到 Interface 接口,然后通過 Interface 來訪問功能函數(shù)。
(3) 并不是每個系統(tǒng)上都實現(xiàn)了 OpenSL ES 為 Object 定義的所有 Interface,所以在獲取 Interface 的時候需要做一些選擇和判斷。

5.2 OpenSL ES的狀態(tài)機制

OpenSL ES 有一個重要的概念:狀態(tài)機制。如圖所示:


OPENSL_ES_Object_state.PNG

任何一個 OpenSL ES 的對象,創(chuàng)建成功后,都進入 SL_OBJECT_STATE_UNREALIZED 狀態(tài),這種狀態(tài)下,系統(tǒng)不會為它分配任何資源。

Realize 后的對象,就會進入 SL_OBJECT_STATE_REALIZED 狀態(tài),這是一種“可用”的狀態(tài),只有在這種狀態(tài)下,對象的各個功能和資源才能正常地訪問。

當一些系統(tǒng)事件發(fā)生后,比如出現(xiàn)錯誤或者 Audio 設備被其他應用搶占,OpenSL ES 對象會進入 SL_OBJECT_STATE_SUSPENDED 狀態(tài),如果希望恢復正常使用,需要調用 Resume 函數(shù)。

當調用對象的 Destroy 函數(shù)后,則會釋放資源,并回到 SL_OBJECT_STATE_UNREALIZED 狀態(tài)。

Engine對象是OpenSL ES API的入口點,這個對象使你能夠創(chuàng)建OpenSL ES中所有其他對象。

Engine對象由全局的對象slCreateEngine()創(chuàng)建得到,創(chuàng)建的結果是Engine對象的一個SLObjectItf的接口。

5.3 Engine Object 和 SLEngineItf Interface

Engine Object是OpenSL ES 里面最核心的對象,
它主要提供如下兩個功能:
(1) 管理 Audio Engine 的生命周期。
(2) 提供管理接口: SLEngineItf,該接口可以用來創(chuàng)建所有其他的 Object 對象。
(3) 提供設備屬性查詢接口:SLEngineCapabilitiesItf 和 SLAudioIODeviceCapabilitiesItf,這些接口可以查詢設備的一些屬性信息。

Engine Object 對象的創(chuàng)建和銷毀的方法如下:

//創(chuàng)建
SLObjectItf engineObject;
slCreateEngine( &engineObject, 0, nullptr, 0, nullptr, nullptr );
//初始化
(*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
//銷毀
(*engineObject)->Destroy(engineObject);

slCreateEngine的函數(shù)定義如下:

SLresult SLAPIENTRY slCreateEngine(
SLObjectItf *pEngine,
SLuint32 numOptions
constSLEngineOption *pEngineOptions,
SLuint32 numInterfaces,
constSLInterfaceID *pInterfaceIds,
constSLboolean *pInterfaceRequired
)

參數(shù)說明如下:
pEngine:指向輸出的engine對象的指針。
numOptions:可選配置數(shù)組的大小。
pEngineOptions:可選配置數(shù)組。
numInterfaces:對象要求支持的接口數(shù)目,不包含隱含的接口。
pInterfaceId:對象需要支持的接口id的數(shù)組。
pInterfaceRequired:指定每個要求接口的接口是可選或者必須的標志位數(shù)組。如果要求的接口沒有實現(xiàn),創(chuàng)建對象會失敗并返回錯誤碼
SL_RESULT_FEATURE_UNSUPPORTED。

獲取管理接口:

SLEngineItf engineEngine;
(*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &(engineEngine));

下面就可以使用 engineEngine 來創(chuàng)建所有 OpenSL ES 的其他對象了。

5.4 Media Object

Media Object代表著多媒體處理功能的抽象,如呈現(xiàn)和捕獲媒體流的對象player、recorder 等等。

可以通過 SLEngineItf 提供的 CreateAudioPlayer 方法來創(chuàng)建一個 player 對象實例,可以通過 SLEngineItf 提供的 CreateAudioRecorder 方法來創(chuàng)建一個 recorder 實例。

5.5 Data Source 和 Data Sink

數(shù)據(jù)源(Data source)是媒體對象的輸入?yún)?shù),指定媒體對象將從何處接收特定類型的數(shù)據(jù)(例如采樣的音頻或MIDI數(shù)據(jù))。 數(shù)據(jù)接收器(Data sink)是媒體對象的輸入?yún)?shù),指定媒體對象將發(fā)送特定類型數(shù)據(jù)的位置。

OpenSL ES 里面,這兩個結構體均是作為創(chuàng)建 Media Object 對象時的參數(shù)而存在的,Data source 代表著輸入源的信息,即數(shù)據(jù)從哪兒來、輸入的數(shù)據(jù)參數(shù)是怎樣的;而 Data Sink 代表著輸出的信息,即數(shù)據(jù)輸出到哪兒、以什么樣的參數(shù)來輸出。

Data Source 的定義如下:

typedef struct SLDataSource_ {
      void *pLocator;
      void *pFormat;
} SLDataSource;

Data Sink 的定義如下:

typedef struct SLDataSink_ {
    void *pLocator;
    void *pFormat;
} SLDataSink;

其中,pLocator 主要有如下幾種:

SLDataLocator_Address
SLDataLocator_BufferQueue
SLDataLocator_IODevice
SLDataLocator_MIDIBufferQueue
SLDataLocator_URI

也就是說,Media Object 對象的輸入源/輸出源,既可以是 URL,也可以 Device,或者來自于緩沖區(qū)隊列等等,完全是由 Media Object 對象的具體類型和應用場景來配置。

數(shù)據(jù)格式(data format)標識數(shù)據(jù)流的特征,包括以下幾種類型:

  • 基于MIME類型的格式
  • PCM格式

5.6 Metadata Extractor Object

播放器對象支持讀取媒體內容的元數(shù)據(jù)。但是有時候只是讀取元數(shù)據(jù)而不播放媒體內容是很有用處的。
Metadata Extractor Object可以用于讀取元數(shù)據(jù)而不需要分配用于媒體播放的資源。

5.7 示例說明

(1) 音頻播放場景:


OPENSL_ES_playback_audio.PNG

使用了Audio Player對象來實現(xiàn)播放音頻功能。使用engine對象的 SLEngineItf接口來創(chuàng)建Audio Player,創(chuàng)建之后與Output mix相關聯(lián)用于音頻輸出。輸入以URI作為示例,Output Mix默認與系統(tǒng)相關的默認輸出設備關聯(lián)。

(2) 錄制音頻場景:


OPENSL_ES_audio_recorder.PNG

通過Audio Recorder對象來實現(xiàn)音頻錄制功能。

6. 示例

OpenSL ES播放PCM數(shù)據(jù)主要有如下7個步驟:
1.創(chuàng)建EngineObject
2.設置DataSource
3.設置DataSink
4.創(chuàng)建播放器
5.設置緩沖隊列和回調函數(shù)
6.設置播放狀態(tài)
7.啟動回調函數(shù)

6.1 創(chuàng)建接口對象

SLresult OpenGLESPlayer::createEngine() {
    LOGD("createEngine()"); 
    SLresult result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
    if(result != SL_RESULT_SUCCESS) {
        LOGD("slCreateEngine failed, result=%d", result);
        return result;
    }
    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    if(result != SL_RESULT_SUCCESS) {
        LOGD("engineObject Realize failed, result=%d", result);
        return result;
    }
    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &(engineEngine));
    if(result != SL_RESULT_SUCCESS) {
        LOGD("engineObject GetInterface failed, result=%d", result);
        return result;
    }
    return result;
}

6.2 設置混音器

    // set DataSource
    SLDataLocator_AndroidSimpleBufferQueue android_queue={SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,2};
    SLDataFormat_PCM sLDataFormat_pcm={
            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 = {&android_queue, &sLDataFormat_pcm};

6.3 設置DataSink

    //set DataSink
    const SLInterfaceID mids[1] = {SL_IID_ENVIRONMENTALREVERB};
    const SLboolean mreq[1] = {SL_BOOLEAN_FALSE};
    ret = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, mids, mreq);
    if(ret != SL_RESULT_SUCCESS) {
        LOGD("CreateOutputMix failed, ret=%d", ret);
        return ret;
    }
    ret = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    if(ret != SL_RESULT_SUCCESS) {
        LOGD("Realize failed, result=%d", ret);
        return ret;
    }
    SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    SLDataSink audioSnk = {&outputMix, NULL};

6.4 創(chuàng)建播放器

    const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};
    const SLboolean req[1] = {SL_BOOLEAN_TRUE};
    ret = (*engineEngine)->CreateAudioPlayer(engineEngine, &playerObject, &slDataSource, &audioSnk, 1, ids, req);
    if (ret != SL_RESULT_SUCCESS) {
        LOGD("CreateAudioPlayer() failed.");
        return ret;
    }
    ret = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
    if (ret != SL_RESULT_SUCCESS) {
        LOGD("playerObject Realize() failed.");
        return ret;
    }
    ret = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay);
    if (ret != SL_RESULT_SUCCESS) {
        LOGD("playerObject GetInterface(SL_IID_PLAY) failed.");
        return ret;
    }

6.5 設置緩沖隊列和回調函數(shù)

    ret = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &simpleBufferQueue);
    if (ret != SL_RESULT_SUCCESS) {
        LOGD("playerObject GetInterface(SL_IID_BUFFERQUEUE) failed.");
        return ret;
    }

    ret = (*simpleBufferQueue)->RegisterCallback(simpleBufferQueue, pcmBufferCallBack, this);
    if (ret != SL_RESULT_SUCCESS) {
        LOGD("SLAndroidSimpleBufferQueueItf RegisterCallback() failed.");
        return ret;
    }
    return ret;
int64_t getPcmData(void **pcm, FILE *pcmFile, uint8_t *out_buffer) {
    while(!feof(pcmFile)) {
        size_t size = fread(out_buffer, 1, 44100 * 2 * 2, pcmFile);
        *pcm = out_buffer;
        return size;
    }
    return 0;
}

void pcmBufferCallBack(SLAndroidSimpleBufferQueueItf bf, void * context) {
    int32_t size = getPcmData(&buffer, pcmFile, out_buffer);
    LOGD("pcmBufferCallBack, size=%d", size);
    if (NULL != buffer && size > 0) {
        SLresult result = (*simpleBufferQueue)->Enqueue(simpleBufferQueue, buffer, size);
    }
}

6.6 設置播放狀態(tài)

void OpenGLESPlayer::start() {
    (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
//    主動調用回調函數(shù)開始工作
    pcmBufferCallBack(simpleBufferQueue, this);
}

void OpenGLESPlayer::stop() {
    LOGD("stop");
    (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_STOPPED);
}

6.7 啟動回調函數(shù)

void OpenGLESPlayer::start() {
    (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
//    主動調用回調函數(shù)開始工作
    pcmBufferCallBack(simpleBufferQueue, this);
}

完整示例:Github

7.參考閱讀

  1. opensles官網(wǎng)
  2. opensels wikipedia
  3. android-audio-high-performance/guides
  4. native-audio github
  5. Android* Low-Latency Audio on x86-based Mobile Devices
  6. OpenSL_ES_Specification_1.0
  7. 使用 OpenSL ES API
  8. MIDI百度百科
  9. OpenSLES_Android.h 源碼
  10. Android通過OpenSL ES播放音頻套路詳解
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容

  • Android音頻系統(tǒng)詳解 參考好文: Android 音頻系統(tǒng):從 AudioTrack 到 AudioFlin...
    愛雨520閱讀 13,741評論 2 7
  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網(wǎng)絡請求組件 FMDB本地數(shù)據(jù)庫組件 SD...
    陽明AGI閱讀 16,003評論 3 119
  • 走吧,帶著一路風塵 去尋找世間的純真 拋卻心頭的擔憂 一如既往的向遠方奔走 走吧,帶著昨天的云 今早的天空怎么少了...
    萬里西風烈閱讀 2,885評論 16 69
  • 有一點猶豫著寫下這個標題,這個標題很大。大到?jīng)]法寫。 每個人都有自己對生死的看法和感悟。曾經(jīng)有一段時間老是想到死,...
    飛翔的皮卡丘閱讀 214評論 0 0
  • 嗨,這是第多少次來到我夢里了呢。是2017年12月,還是喜歡你的第五個不被知道的年分加上想不起的三個月和兩個明不清...
    9th_of_October閱讀 322評論 0 0