android平臺OpenSL ES播放PCM數據

目錄

  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ā)者提供標準化扯键、高性能珊肃、低延遲的方法來實現音頻功能伦乔,并致力于跨多個平臺輕松移植應用程序。OpenSL ES由非營利性技術聯(lián)盟Khronos Group管理。

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

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

2. 主要功能

OpenSL ES主要功能包括:

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

3. Android 平臺的OpenSL ES

Android 2.3將OpenSL ES 1.0.1作為其NDK的一部分。在之后的版本中闸翅,實現的延遲有所改進菊霜。

Android 實現的 OpenSL ES 只是 OpenSL ES 1.0.1 的子集鉴逞,并且進行了擴展。因此辙纬,對于 OpenSL ES API 的使用叭喜,我們需要特別留意哪些是 Android 支持的捂蕴,哪些是不支持的闪幽。


image

不支持的功能:

  • 不支持 MIDI盯腌。
  • 不支持直接播放 DRM 或者 加密的內容。
  • 不支持音頻數據的編解碼级乍,如需編解碼帚湘,需要使用 MediaCodec API 或者第三方庫大诸。
  • 在音頻延時方面贯卦,相比于JAVA的 API焙贷,并沒有特別明顯地改進盈厘。

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

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

5. API簡要介紹

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

它的大都數 API 需要這樣訪問:

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

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

5.1 Object 和 Interface

OpenSL ES 有兩個重要的概念 Object 和 Interface昧港,“對象”和“接口”创肥。
(1) 每個 Object 可能會存在一個或者多個 Interface,官方為每一種 Object 都定義了一系列的 Interface巩搏。
(2) 每個 Object 對象都提供了一些最基礎的操作,比如:Realize棘幸,Resume禽捆,GetState飘哨,Destroy 等等,如果希望使用該對象支持的功能函數浊服,則必須通過其 GetInterface 函數拿到 Interface 接口摆马,然后通過 Interface 來訪問功能函數囤采。
(3) 并不是每個系統(tǒng)上都實現了 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ā)生后,比如出現錯誤或者 Audio 設備被其他應用搶占步责,OpenSL ES 對象會進入 SL_OBJECT_STATE_SUSPENDED 狀態(tài)蔓肯,如果希望恢復正常使用振乏,需要調用 Resume 函數。

當調用對象的 Destroy 函數后调限,則會釋放資源耻矮,并回到 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的函數定義如下:

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

參數說明如下:
pEngine:指向輸出的engine對象的指針俱病。
numOptions:可選配置數組的大小袱结。
pEngineOptions:可選配置數組垢夹。
numInterfaces:對象要求支持的接口數目棚饵,不包含隱含的接口掩完。
pInterfaceId:對象需要支持的接口id的數組。
pInterfaceRequired:指定每個要求接口的接口是可選或者必須的標志位數組欣硼。如果要求的接口沒有實現诈胜,創(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代表著多媒體處理功能的抽象缓熟,如呈現和捕獲媒體流的對象player、recorder 等等够滑。

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

5.5 Data Source 和 Data Sink

數據源(Data source)是媒體對象的輸入參數分蓖,指定媒體對象將從何處接收特定類型的數據(例如采樣的音頻或MIDI數據)咆疗。 數據接收器(Data sink)是媒體對象的輸入參數母债,指定媒體對象將發(fā)送特定類型數據的位置毡们。

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

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 對象的具體類型和應用場景來配置箭跳。

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

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

5.6 Metadata Extractor Object

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

5.7 示例說明

(1) 音頻播放場景:


OPENSL_ES_playback_audio.PNG

使用了Audio Player對象來實現播放音頻功能刨晴。使用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對象來實現音頻錄制功能蝶桶。

6. 示例

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

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 設置緩沖隊列和回調函數

    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);
//    主動調用回調函數開始工作
    pcmBufferCallBack(simpleBufferQueue, this);
}

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

6.7 啟動回調函數

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

完整示例:Github

7.參考閱讀

  1. opensles官網
  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)系作者
  • 序言:七十年代末慨绳,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子真竖,更是在濱河造成了極大的恐慌脐雪,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恢共,死亡現場離奇詭異战秋,居然都是意外死亡,警方通過查閱死者的電腦和手機旁振,發(fā)現死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門获询,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涨岁,“玉大人拐袜,你說我怎么就攤上這事吉嚣。” “怎么了蹬铺?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵尝哆,是天一觀的道長。 經常有香客問我甜攀,道長秋泄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任规阀,我火速辦了婚禮恒序,結果婚禮上,老公的妹妹穿的比我還像新娘谁撼。我一直安慰自己歧胁,他們只是感情好,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布厉碟。 她就那樣靜靜地躺著喊巍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪箍鼓。 梳的紋絲不亂的頭發(fā)上崭参,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天,我揣著相機與錄音款咖,去河邊找鬼何暮。 笑死,一個胖子當著我的面吹牛铐殃,可吹牛的內容都是我干的郭卫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼背稼,長吁一口氣:“原來是場噩夢啊……” “哼贰军!你這毒婦竟也來了?” 一聲冷哼從身側響起蟹肘,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤词疼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后帘腹,有當地人在樹林里發(fā)現了一具尸體贰盗,經...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年阳欲,在試婚紗的時候發(fā)現自己被綠了舵盈。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陋率。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖秽晚,靈堂內的尸體忽然破棺而出瓦糟,到底是詐尸還是另有隱情,我是刑警寧澤赴蝇,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布菩浙,位于F島的核電站,受9級特大地震影響句伶,放射性物質發(fā)生泄漏劲蜻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一考余、第九天 我趴在偏房一處隱蔽的房頂上張望先嬉。 院中可真熱鬧,春花似錦楚堤、人聲如沸疫蔓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鳄袍。三九已至,卻和暖如春吏恭,著一層夾襖步出監(jiān)牢的瞬間拗小,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工樱哼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留哀九,地道東北人。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓搅幅,卻偏偏與公主長得像阅束,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子茄唐,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內容

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