1. 程式人生 > >Android AudioRecord和AudioTrack介紹

Android AudioRecord和AudioTrack介紹

Android音訊收集和播放()

一、文章說明

這篇文章主要講述的是Android中使用AudioRecord類和AudioTrack類來進行語音採集和播放相關的知識,在這篇文章中首先介紹的是有關聲音的一些概念性知識,然後介紹聲音的採集,之後再講述Android上回聲消除的相關步驟,最後介紹的是聲音的播放。

二、概念性知識點

在這裡關於聲音的定義和產生就不再贅述了,如果有對這個感興趣的朋友可以去了解一下,下面介紹幾個聽過但是不是很清楚的概念。

麥克風降噪

大集都知道,在現在這個科技盛行的年代,在打電話時,即使一方的環境有些嘈雜,而在電話的另一方也能聽的清清楚楚,這主要靠的就是手機降噪技術的發展。目前所使用的手機不僅僅有一個麥克風,而是有2個甚至是3個(iPhone),這多出的麥克風就是降噪的關鍵。手機麥克風降噪圖請看圖一:

 
圖一 手機麥克風降噪

下面以兩個麥克風的情況來說一下麥克風降噪,頂部和底部都各有一個。這兩個麥克風都很小,但是兩者的作用是有著非常明顯的區別,其中底部的麥克風是保證清晰通話的,而頂部的麥克風是用來消除噪音的。

由於頂部和底部在通話時和音源(發出聲音的源頭)的距離不同,所以兩個麥克風拾取的音量大小也是有不同的,利用這個差別,我們就可以過濾掉噪聲保留人聲了。在打電話時,兩個麥克風所拾取的背景噪聲音量是基本相同的,而記錄的人聲會有6dB左右的音量差。頂端麥收集噪聲後,通過解碼生成補償訊號後就可以用來消除噪音了。

回聲

當聲投射到距離聲源有一段距離的大面積上時,聲能的一部分被吸收,而另一部分聲能要反射回來,如果聽者聽到由聲源直接發來的聲和由反射回來的聲的時間間隔超過十分之一秒(在15℃空氣中,距聲源至少17米處反射),它就能分辨出兩個聲音這種反射回來的聲叫“回聲”。如果聲速已知,當測得聲音從發出到反射回來的時間間隔,就能計算出反射面到聲源之間的距離。例如在室溫(20℃)時空氣中的聲速是343米每秒,所以站在聲源處的人要聽到回聲需要障礙物到聲源的距離至少17米。

回聲消除

很多時候在收集語音的同時也會播放著聲音,這就要在語音收集的時候就需要對採集的聲音進行回聲消除。當在語音收集的時候,如果還同時播放著聲音的話,就不能保證採集的聲音不包括正在播放的聲音,在這種情況下采集的語音即包括原聲又包括回聲,在這樣的惡性迴圈下,就會使得回聲越來越多,最後出現嗡鳴聲。

回聲消除就是在麥克風錄製外音的時候去除掉手機自身播放出來的聲音,這樣就將播放的聲音從採集的聲音中過濾出去,從而就避免了回聲的產生。圖二很好展示了回聲消除的機制。

 
圖二 回聲消除

在近端,麥克風會採集到揚聲器播放出來的遠端聲音,假設這路聲音為y(n),當然由於需要將遠端傳來播放出來,我們當然能得到遠端傳來的聲音訊號,假設這路聲音為x(n)。不難發現x(n)經過揚聲器的播放,然後經過空氣的傳播,最後被麥克風採集,然後變為y(n),x(n)和y(n)具有明顯的相關性。假設麥克風採集到的總聲音訊號為z(n),這時候需要通過自適應濾波器根據x(n)找出z(n)中的y(n),然後從z(n)中過濾掉y(n)。

三、聲音採集

麥克風的原理是將將採集的聲音轉換為模擬電訊號,之後將模擬電訊號數字化,也就是用高低電平表示的訊號(計算機能識別的訊號),在Android中有一個AudioRecord類就能錄製語音,並將語音轉換為PCM資料,聲音在經過麥克風轉換為模擬電訊號並最終又轉換為PCM資料,在轉換為PCM資料時就要依賴於三個引數,分別是:聲道數、取樣位數和取樣頻率。

聲道數

很好理解,有單聲道和立體聲之分,單聲道的聲音只能使用一個喇叭發聲(有的也處理成兩個喇叭輸出同一個聲道的聲音),立體聲的PCM可以使兩個喇叭都發聲(一般左右聲道有分工) ,更能感受到空間效果。

取樣位數

即取樣值或取樣值(就是將取樣樣本幅度量化)。它是用來衡量聲音波動變化的一個引數,也可以說是音效卡的解析度。它的數值越大,解析度也就越高,所發出聲音的能力越強。

在計算機中取樣位數一般有8位和16位之分,但有一點請大家注意,8位不是說把縱座標分成8份,而是分成2的8次方即256份; 同理16位是把縱座標分成2的16次方65536份。

取樣頻率

即取樣頻率,指每秒鐘取得聲音樣本的次數。取樣頻率越高,聲音的質量也就越好,聲音的還原也就越真實,但同時它佔的資源比較多。由於人耳的解析度很有限,太高的頻率並不能分辨出來。在16位音效卡中有22KHz、44KHz等幾級,其中,22KHz相當於普通FM廣播的音質,44KHz已相當於CD音質了,目前的常用取樣頻率都不超過48KHz。

既然知道了以上三個概念,就可以由下邊的公式得出PCM檔案所佔容量:

儲存量= (取樣頻率 · 取樣位數 · 聲道 · 時間)/8 (單位:位元組數)。

四、Android聲音錄製

Android中使用AudioRecord和MediaRecorder進行音訊的錄製,這裡介紹AudioRecord類的使用,有朋友對MediaRecorder感興趣的就去學習一下。根據上邊的介紹可知,需要給AudioRecord傳入取樣頻率、取樣位數和聲道數,除此之外還需要傳入兩個引數,一個是聲音源,一個是緩衝區大小。在這裡緩衝區大小不能小於最小緩衝區大小(下面有介紹)。

許可權

在Android中進行音訊的錄製是需要相應的許可權的。在Android 6.0及以上要動態的申請許可權。

音訊源

下面是Android所支援的音訊源:

/**預設聲音**/

public static final int DEFAULT = 0;

/**麥克風聲音*/

public static final int MIC = 1;

/**通話上行聲音*/

public static final int VOICE_UPLINK = 2;

/**通話下行聲音*/

public static final int VOICE_DOWNLINK = 3;

/**通話上下行聲音*/

public static final int VOICE_CALL = 4;

/**根據攝像頭轉向選擇麥克風*/

public static final int CAMCORDER = 5;

/**對麥克風聲音進行聲音識別,然後進行錄製*/

public static final int VOICE_RECOGNITION = 6;

/**對麥克風中類似ip通話的交流聲音進行識別,預設會開啟回聲消除和自動增益*/

public static final int VOICE_COMMUNICATION = 7;

/**錄製系統內建聲音*/

public static final int REMOTE_SUBMIX = 8;

在我的專案中音訊源來自於麥克風聲音。

緩衝區大小

設定完音訊源接下來就要設定緩衝區大小。麥克風採集到的音訊資料先放置在一個緩衝區裡面,之後我們再從這個緩衝區裡面讀取資料,從而獲取到麥克風錄製的音訊資料。在Android中不同的聲道數、取樣位數和取樣頻率會有不同的最小緩衝區大小,當AudioRecord傳入的緩衝區大小小於最小緩衝區大小的時候則會初始化失敗。大的緩衝區大小可以開啟更為平滑的錄製效果,相應的也會帶來更大一點的延時。如下圖所示展示了緩衝區在音訊錄製和獲取中所處的位置:

 
圖三 緩衝區大小

這裡提到的最小緩衝區的大小可以由下邊的程式碼來獲得:

AudioRecord.getMinBufferSize(frequency, channelConfiguration,audioEncoding);

當獲取最小緩衝區大小失敗的時候,會返回相應的負數錯誤碼。

AudioRecord的初始化

下面的程式碼是AudioRecord的初始化處理

public AudioRecord(int audioSource, int sampleRateInHz, intchannelConfig, int audioFormat, int bufferSizeInBytes) throwsIllegalArgumentException

這個構造方法中的引數上邊已經有了講解。當初始化失敗時也就是傳入的引數不匹配時就會丟擲異常。如果想知道是否初始化成功的話可以獲取一下AudioRecord的一個狀態量,通過getState()方法可以獲取,當返回為STATE_UNINITIALIZED表示未成功初始化,當返回為STATE_INITIALIZED表示已經成功初始化了,初始化成功後就可以進行讀取緩衝區中的音訊資料的操作了。

讀取資料

AudioRecord使用read()方法進行資料的讀取操作,正如下面的這個方法:

public int read(@NonNull byte[] audioData, int offsetInBytes,int sizeInBytes) {

returnread(audioData, offsetInBytes, sizeInBytes, READ_BLOCKING);

}

當從緩衝區獲取音訊資料失敗時,會返回負數的錯誤碼。

引數選擇

眾所周知Android的廠商非常多,這就給開發者們製造了很多的麻煩,最為迫切解決的也就是相容性的問題,在這方面Android推薦在錄音時使用的引數為:

sampleRateInHz = 44100;//取樣率

channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;//音道數

audioFormat = AudioFormat.ENCODING_PCM_16BIT//取樣位數

五、Android回聲消除

在Android中回聲消除可以通過三種方式進行處理:1、通過VOICE_COMMUNICATION模式進行錄音,自動實現回聲消除;2、利用Android自身帶的AcousticEchoCanceler進行回聲消除處理;3、使用第三方庫(Speex、Webrtc)進行回聲消除處理(在專案中我所使用的就是Speex進行回聲處理的)。

通過第一種方式進行回聲消除,需要將AudioManager設定模式為MODE_IN_COMMUNICATION,還需要將麥克風開啟。有一點需要特別注意,音訊取樣率必須設定8000或者16000,通道數必須設為1個(別問為什麼,這就是規定)。

AudioManager audioManager =(AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);

audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);

audioManager.setSpeakerphoneOn(true);

使用AcousticEchoCanceler過程比較簡單,錄製聲音的時候可以通過AudioRecord得到AudioSessionId,在建立AudioTrack的時候也可以傳入一個AudioSessionId,這時候將這個統一的AudioSessionId傳入AcousticEchoCanceler,那麼AcousticEchoCanceler將根據之前講過的回聲消除的原理進行回聲消除。程式碼如下:

 
AcousticEchoCanceler回聲消除程式碼

當使用Speex或者Webrtc第三方庫進行回聲消除的時候,需要將採集到的音訊資料傳入作為源資料,需要將此刻播放的音訊資料傳入作為參考資料,然後還需要傳入一個延時間隔,這樣第三方庫就能工作,從而得到回聲消除後的聲音。因為播放的聲音需要傳播,而且麥克風採集聲音還有相應的緩衝區,因此需要傳入一個延時間隔。關於Speex和Webrtc在github上能找到相應的Android ndk庫。有興趣的朋友可以實際動手進行測試一下。

六、Android聲音播放

前面介紹了Android的音訊錄製,下面介紹一下音訊的播放,同樣Android有兩個音訊播放的類MediaPlayer和AudioTrack,這裡介紹AudioTrack類,其實MediaPlayer在framwork層也例項化了AudioTrack來進行解碼生成PCM,然後代理給AudioTrack。

StreamType(指定在流的型別)

這個在構造AudioTrack的第一個引數中使用。這個引數和Android中的AudioManager有關係,涉及到手機上的音訊管理策略。Android將系統的聲音分為以下幾類常見的:

STREAM_ALARM:警告聲

STREAM_MUSCI:音樂聲,例如music等

STREAM_RING:鈴聲

STREAM_SYSTEM:系統聲音

STREAM_VOCIE_CALL:電話聲音

模式型別

這個在構造AudioTrack的最後一個引數中使用。AudioTrack中有MODE_STATIC和MODE_STREAM兩種分類。STREAM方式表示由使用者通過write方式把資料一次一次得寫到audiotrack中。這種方式的缺點就是JAVA層和Native層不斷地交換資料,效率損失較大。而STATIC方式表示是一開始建立的時候,就把音訊資料放到一個固定的buffer,然後直接傳給audiotrack,後續就不用一次次得write了。AudioTrack會自己播放這個buffer中的資料。這種方法對於鈴聲等體積較小的檔案比較合適。

AudioTrack的初始化

下面的程式碼是AudioTrack的初始化處理

public AudioTrack(int streamType,int sampleRateInHz, int channelConfig,

int audioFormat,int bufferSizeInBytes,int mode)throws IllegalArgumentException

{}這個構造方法中的引數上邊已經有了講解。當初始化失敗時也就是傳入的引數不匹配時就會丟擲異常。

寫入資料

AudioTrack使用write()方法將資料寫入到audiotrack中,正如下面的這個方法:

public int write(byte[] audioData,int offsetInBytes, int

sizeInBytes) {}offsetInBytes是指要播放的資料是從引數audioData的哪個地方開始。

七、總結

好了,這個第一篇就寫到這裡了,希望朋友們能對Android錄音和播放有一定的瞭解。後續還會更新。