Android的聲音編程--使用OpenSL ES Audio

OpenSL ES? 是無授權費、跨平臺、針對嵌入式系統精心優化的硬件音頻加速API。Android NDK 給出了使用OpenSL ES來實現native-audio的例子。本文介紹瞭如何使用OpenSL ESAndroid中進行聲音編程。從OpenSL ES的編程編譯鏈接設置到創建聲音引擎、創建聲音播放器、設置播放緩衝等具體步驟。

編譯和鏈接設置

使用OpenSL ES的前提條件是在源代碼中包含 OpenSL ES的頭文件如下所示
#include <SLES/OpenSLES.h>
gcc中的鏈接選項link option中鏈接OpenSL ES的庫文件
-lOpenSLES 
OpenSL ES音頻編程步驟

具體的使用步驟如下:
1.
創建聲音引擎
2.
創建聲音播放器
3.
設置播放緩衝

OpenSL ES的接口

OpenSL ES 主要操作的是接口(interface),大體來說有如下幾個接口:
SLObjectItf 
:對象接口, 
SLEngineItf 
:引擎接口
SLPlayItf
:播放接口
SLBufferQueueItf :
緩衝隊列接口
SLEffectSendItf
:音效發送接口
SLMuteSoloItf : 
聲道關閉 /單聲道接口
SLVolumeItf : 
聲量接口
初始化聲音引擎
除了 slCreateEngine 這一條函數是用來創建聲音引擎對象接口以外,其它的所有操作都是通過接口的成員函數完成的,現在研究如何初始化聲音引擎

初始化聲音引擎需要3個接口,要將它們聲明爲全局變量:
SLObjectItf _aud;/* 
聲音引擎的對象接口 */
SLEngineItf _aud_eng;/* 
聲音引擎 */
SLObjectItf _aud_mix;/* 
混音器對象接口 */

首先創建聲音引擎的對象接口 
slCreateEngine(&_aud, 0, NULL, 0, NULL, NULL); 
創建之後的接口並不能立即使用,我們首先要通過接口的子函數實現(Realize)它:
(*_aud)->Realize(_aud, SL_BOOLEAN_FALSE);/* 
通過_audRealize子函數實現聲音引擎的對象接口 */
實現之後,從聲音引擎的對象中抓取聲音引擎,在這裏通過接口的子函數抓取接口GetInterface),抓取對像是 _aud,抓取類型是引擎,抓取後存放的內存位置是先前聲明的引擎接口_aud_eng的指針位置。指令如下:
(*_aud)->GetInterface(_aud, SL_IID_ENGINE, &_aud_eng);
這樣聲音引擎就初始化了

第三步要做的是從聲音引擎中創建 ”輸出混音器“ 對象,這樣纔可以將聲音播放出來。
注意,同聲音引擎的對象一樣, 由於 ”輸出混音器“ 是對象,必須創建後在實現(Realize)它。
由於輸出混音器創建的同時需要兩個音效參數,所以先準備好這兩個參數:第一個參數類型是聲音接口編號(SLInterfaceID),是一個數組,可以將其看成一個音效配置列表,在其中放置不同的音效編號。第二個參數是一個邏輯數組:是否強制實現聲音接口編號中對應音效,如果某個音效的邏輯爲真(SL_BOOLEAN_TRUE),則在無法實現該音效時視爲輸出混音器創建失敗,否則將忽略該音效。因爲安卓的聲硬件對音效的支持不同,所以最好不要強制實現,所以在第二個數組中全部填寫SL_BOOLEAN_FALSE 

現在開始創建輸出混音器,環境迴響(SL_IID_ENVIRONMENTALREVERB )是比較常見的音效,將非強制性的使用該音效。

SLInterfaceID effect[1] = {SL_IID_ENVIRONMENTALREVERB}; /*
環境迴響音效 */
SLboolean effect_bool[1] = {SL_BOOLEAN_FALSE}; /* 
迴響音效強制實現邏輯 */
(*_aud_eng)->CreateOutputMix(_aud_eng, &_aud_mix, 1, effect, effect_bool);/* 
從聲音引擎中創建輸出混音器” */
(*_aud_mix)->Realize(_aud_mix, SL_BOOLEAN_FALSE); /* 
實現剛創建的輸出混音器” */

這樣一來,所有的初始化工作就全部完成了。完整代碼如下:

SLObjectItf _aud;/* 
聲音引擎對象 */
SLEngineItf _aud_eng;/* 
聲音引擎 */
SLObjectItf _aud_mix;/* 
輸出混音器對象 */

/* audio_init: 
初始化OpenSL ES */
int audio_init ()
{
SLInterfaceID effect[1] = {SL_IID_ENVIRONMENTALREVERB}; /* 
音效 */
SLboolean effect_bool[1] = {SL_BOOLEAN_FALSE}; /*
音效強制實現邏輯 */

slCreateEngine(&_aud, 0, NULL, 0, NULL, NULL);/* 
創建聲音引擎對象 */
(*_aud)->Realize(_aud, SL_BOOLEAN_FALSE);/* 
實現聲音引擎對象 */
(*_aud)->GetInterface(_aud, SL_IID_ENGINE, &_aud_eng);/* 
從聲音引擎對象中抓取聲音引擎 */
(*_aud_eng)->CreateOutputMix(_aud_eng, &_aud_mix, 1, effect, effect_bool);/* 
通過聲音引擎創建輸出混音器對象,並且非強制性的開啓環境混響效果 */
(*_aud_mix)->Realize(_aud_mix, SL_BOOLEAN_FALSE);/* 
實現混音器對象 */

return;
}

創建播放器對象

初始化引擎之後,還要創建一個播放器對象,就可以在播放器中輸出聲音了
播放器對象和輸出混音器一樣,是對象,創建後需要進行實現:
SLObjectItf _aud_plyobj; /*
播放器對象 */
(*_aud_eng)->CreateAudioPlayer(_aud_eng, &_aud_plyobj, &sndsrc, &sndsnk, 3, ids, req);
/* 
這是創建聲音播放器對象aud_plyobj的函數 */
前兩個參數分別把 聲音引擎  播放器引擎的指針位置填上就可以了
真正需要填寫的是後面4個參數  
sndsrc: 
聲音數據源
sndsnk:
聲音輸出池(data sink),也就是聲音輸出設備
3:
功能清單數目,我們要讓播放器支持3種功能
ids: 
功能清單,要讓播放器支持的功能
req:
功能強制實現邏輯
下面逐一來分析這5個參數

sndsrc是聲源,類型是SLDataSource, 是一個有兩個成員的結構,兩個成員分別是:
pLocator: 
數據定位器(DataLocator)的指針位置
PFormat:
數據定位器指向數據的格式指針位置,在這裏使用pcm 聲音格式
那麼首先看這兩種結構: 
1:
數據定位器 就是定位要播放聲音數據的存放位置,分爲4種:內存位置,輸入/輸出設備位置,緩衝區隊列位置,和midi緩衝區隊列位置。由於我們可以靈活的對緩衝區隊列進行安排並設置播放後回傳函數,而且我們使用的是pcm聲音格式,所以我們採取第三種數據定位器:
SLDataLocator_BufferQueue loc_bufq; /* 
緩衝區隊列定位器 */
2:
數據格式,由於我們採用的是pcm聲音格式,所以我們使用:
SLDataFormat_PCM format_pcm; /* pcm 
數據格式 */
接下來,我們要填寫 ”緩衝區隊列定位器“  “pcm數據格式“ 這兩種結構的內容 

我們要在SLDataLocator_BufferQueue類型變量 loc_bufq中填上的是
SLDataLocator_BufferQueue loc_bufq = { /* 
緩衝區隊列定位器 */ 
SL_DATALOCATOR_BUFFERQUEUE, /* 
必須填成左邊這樣,表示數據定位器將定位緩衝區隊列 */ 
2 /* 
緩衝隊列裏的緩衝數目,這裏我們填寫2個緩衝 */ 
};
我們要在SLDataFormat_PCM類型變量 format_pcm中填上的是
SLDataFormat_PCM format_pcm = { /* pcm 
數據格式 */
SL_DATAFORMAT_PCM, /* 
必須填成左邊這樣,表示pcm 數據格式 */
1, /* 
這裏是聲道數目,多聲道數目將交錯存放,而不是整體存放,我們使用單聲道 */
SL_SAMPLINGRATE_8, /*
採樣率,我們使用 8kHz的採樣率,質量不是很好 */
SL_PCMSAMPLEFORMAT_FIXED_16, /*
採樣大小,我們使用固定的 16bit採樣 */ 
SL_PCMSAMPLEFORMAT_FIXED_16,/*
容器大小,和採樣大小保持一致就不會失真,我們採用 16bit容器*/ SL_SPEAKER_FRONT_CENTER, /* 聲道映射掩碼,在這裏,我們將不用的聲道映射到不同的擴音器中,在這裏,由於我們的聲音是單聲道,所以我們將其映射到安卓設備的主擴音器也就是前中擴音器:SPEAKER_FRONT_CENTER */
SL_BYTEORDER_LITTLEENDIAN /* 
尾序:小端模式 */
};

 

這樣,我們這完成了聲源參數:
sndsrc
我們還要完成剩下的4個參數,也就是:
sndsnk:
聲音輸出池(data sink),也就是聲音輸出設備
3:
功能清單數目,我們要讓播放器支持3種功能
ids: 
功能清單,我們要讓播放器支持的功能
req:
功能強制實現邏輯 

從聲音輸出池開始,聲音輸出池和聲源的成員差不多:
pLocator: 
數據定位器(DataLocator)的指針位置
PFormat:
數據定位器指向數據的格式指針位置

但聲音輸出池需要的是SLDataLocator_OutputMix 輸出混音器數據定位器,而且不需要交代數據格式,所以只要填寫輸出混音器數據定位器就可以了:

SLDataLocator_OutputMix loc_outmix = { /* 
輸出混音器數據定位器 */ 
SL_DATALOCATOR_OUTPUTMIX, /* 
必須填成左邊這樣,表示數據定位器將定位輸出混音器 */ 
NULL /* 
不需要交代數據格式 */ 
};

 

最後只有播放器功能清單要完成了,我們要求播放器具備3種功能 ,它們的編號如下:
SL_IID_BUFFERQUEUE
:緩衝區隊列功能(循環播放,隨機播放…)
SL_IID_EFFECTSEND
:音效功能(環境迴響,均衡器…)

SL_IID_VOLUME
:音量功能(音量調節,3d聲源…)
這樣來填寫 
SLInterfaceID ids[] = { /* 
播放器功能列表 */
SL_IID_BUFFERQUEUE, /* 
緩衝區隊列功能 */
SL_IID_EFFECTSEND,/* 
音效功能 */
SL_IID_VOLUME /* 
音量功能 */
};
而強制邏輯和之前提到的req完全一樣,但這3種功能是常用功能,一般的安卓設備都應具備,所以我們強制要求實現,實現不了則視爲錯誤:
SLboolean req[] = {
SL_BOOLEAN_TRUE, /* 
強制實現id中對應的 緩衝區隊列功能 */
SL_BOOLEAN_TRUE, /* 
強制實現id中對應的 音效功能 */
SL_BOOLEAN_TRUE /* 
強制實現id中對應的 音量功能 */
};

整合以上步驟,我們終於得到了一個聲音播放器對象 _aud_plyobj,最終指令如下:
(*_aud_eng)->CreateAudioPlayer(_aud_eng, &_aud_plyobj, &sndsrc, &sndsnk, 3, ids, req);
/* 
從聲音引擎_aud_eng中創建 聲音播放器對象_aud_plyobj
同其它對象一樣,我們來實現聲音播放器對象_aud_plyobj: 
(*_aud_plyobj)->Realize(_aud_plyobj, SL_BOOLEAN_FALSE); /*
實現聲音播放器對象 */
這樣一來,聲音播放器對象就創建好了。
OpenSL ES
主要操作的是接口(interface),所以要從播放器中獲得一些接口,這樣在對接口進行操作時,就能控制聲音播放器。在這裏,我們從聲音播放器中獲得4種不同類型的接口,首先進行全局變量聲明:
SLPlayItf _aud_ply; /* 
播放接口(播放,暫停 */
SLBufferQueueItf _aud_buf; /*
緩衝區接口 */
SLEffectSendItf _aud_bufefx; /*
音效接口,在本播放器中將用於緩衝區 */
SLVolumeItf _aud_vol; /*
音量接口 */
然後通過GetInterface成員函數從播放器對象中獲得接口:
(*_aud_plyobj)->GetInterface(_aud_plyobj, SL_IID_PLAY, &_aud_ply); /*
獲得播放接口, 存至_aud_ply*/
(*_aud_plyobj)->GetInterface(_aud_plyobj, SL_IID_BUFFERQUEUE, &_aud_buf); /*
獲得緩衝區接口,存至 _aud_buf */
(*_aud_plyobj)->GetInterface(_aud_plyobj, SL_IID_EFFECTSEND, &_aud_bufefx); /*
獲得音效接口,存至 _aud_bufefx */
(*_aud_plyobj)->GetInterface(_aud_plyobj, SL_IID_VOLUME, &_aud_vol); /*
獲得音量接口,存至 _aud_vol */
從現在起,所有對上述4個接口的操作都將影響到播放器

爲了真正實現緩衝區連續播放的功能,我們要爲緩衝區接口_aud_buf註冊一個回調函數:
(*_aud_buf)->RegisterCallback(_aud_buf, _newsnd_cb, NULL);
_newsnd_cb
函數將在緩衝區播放完畢時自動執行,接下來我們研究這個函數怎麼實現

前面創建了引擎和播放器,如果我們命令播放器播放,會不會馬上有聲音呢?答案是否定的,因爲我們根本沒有提供數據

提供播放數據
其實自行設計_newsnd_cb回調函數將要實現的功能只有一個,就是爲播放器提供將要播放的數據,這樣播放器就會自動對其進行播放了
short *snd; /* 16 bit, 8kHz
單聲道 pcm 聲音採樣 */
unsigned sndlen; /*
聲音採樣數據的大小*/
int num_snd; /* 
該採樣的剩餘播放的次數 */

/* _newsnd_cb: 
我們自己創建的緩衝區隊列播放回調函數,第一項填寫目標緩衝區接口,第二項填NULL即可 */
void _newsnd_cb(SLBufferQueueItf bq, void *context)
{
if (–num_snd > 0 && snd && sndlen > 0) /* 
如果採樣的剩餘播放的次數大於0,聲音數據有效,而且 聲音數據的長度大於0 */
(*bq)->Enqueue(bq, snd, sndlen); /* 
操作緩衝區隊列接口,將長度爲 sndlen的聲音數據snd排進隊列 */
}
這樣,緩衝區隊列沒有聲音可以播放時,將呼出上述回調函數,而該回調函數將檢查剩餘播放次數,並在有播放次數的情況下將 聲音 排進隊列,從而讓緩衝區隊列可以播放隊列中的第一個聲音,也就是剛排進去的聲音。當然,由於這個函數是自定義的,靈活度很高,所以大家可以自由發揮,藉助它實現隨機播放,或是互聯網 流媒體播放 

現在 我們完成了 OpenSL ES聲音引擎和播放器創建,只需要操作_aud_ply 播放接口,即可啓動播放器:
(*_aud_ply)->SetPlayState(_aud_ply, SL_PLAYSTATE_PLAYING); /* 
將播放狀態設爲正在播放” */
爲了測試我們的播放器是否正常,我們嘗試通過函數合成一個時長爲一秒種的 正弦波採樣,將其播放5:
#include “math.h”
short snd_sin[8000]; /* 
時長爲1秒的 16bit 單聲道 正弦波採樣 */ 
void create_sine_wave(void)
{
int i;
for (i = 0; i < 8000; ++i) /* 
聲音時長1秒,所以創建8000個單聲道 採樣 */
snd_sin[i] = sinf(i*180/M_PI)*32767; /* 
通過正弦函數創建單個採樣值 */ 
}

這樣就能通過輸出聲音是否爲正弦波來判斷播放器是否正常了

最後是將正弦波播放5次的 演示程序:
http://pan.baidu.com/s/1uw672

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章