Android SDK 提供了3套音頻播放的API,分別是:MediaPlayer,SoundPool,AudioTrack,關於它們的區別可以看這篇文章:《Intro to the three Android Audio APIs》,簡單來說,MediaPlayer 更加適合在后台長時間播放本地音樂文件或者在線的流式資源; SoundPool 則適合播放比較短的音頻片段,比如游戲聲音、按鍵聲、鈴聲片段等等,它可以同時播放多個音頻; 而 AudioTrack 則更接近底層,提供了非常強大的控制能力,支持低延遲播放,適合流媒體和VoIP語音電話等場景。
音頻的開發,更廣泛地應用不僅僅局限於播放本地文件或者音頻片段,因此,本文重點關注如何利AudioTrack API 來播放音頻數據(注意,使用AudioTrack播放的音頻必須是解碼后的PCM數據)。
1. AudioTrack 的工作流程
首先,我們了解一下 AudioTrack 的工作流程:
(1) 配置參數,初始化內部的音頻播放緩沖區
(2) 開始播放
(3) 需要一個線程,不斷地向 AudioTrack 的緩沖區“寫入”音頻數據,注意,這個過程一定要及時,否則就會出現“underrun”的錯誤,該錯誤在音頻開發中比較常見,意味着應用層沒有及時地“送入”音頻數據,導致內部的音頻播放緩沖區為空。
(4) 停止播放,釋放資源
2. AudioTrack 的參數配置
上面是 AudioTrack 的構造函數原型,主要靠構造函數來配置相關的參數,下面一一解釋(再次建議先閱讀一下《Android音頻開發(1):基礎知識》):
(1) streamType
這個參數代表着當前應用使用的哪一種音頻管理策略,當系統有多個進程需要播放音頻時,這個管理策略會決定最終的展現效果,該參數的可選的值以常量的形式定義在 AudioManager 類中,主要包括:
STREAM_VOCIE_CALL:電話聲音
STREAM_SYSTEM:系統聲音
STREAM_RING:鈴聲
STREAM_MUSCI:音樂聲
STREAM_ALARM:警告聲
STREAM_NOTIFICATION:通知聲
(2) sampleRateInHz
采樣率,從AudioTrack源碼的“audioParamCheck”函數可以看到,這個采樣率的取值范圍必須在 4000Hz~192000Hz 之間。
(3) channelConfig
通道數的配置,可選的值以常量的形式定義在 AudioFormat 類中,常用的是 CHANNEL_IN_MONO(單通道),CHANNEL_IN_STEREO(雙通道)
(4) audioFormat
這個參數是用來配置“數據位寬”的,可選的值也是以常量的形式定義在 AudioFormat 類中,常用的是 ENCODING_PCM_16BIT(16bit),ENCODING_PCM_8BIT(8bit),注意,前者是可以保證兼容所有Android手機的。
(5) bufferSizeInBytes
這個是最難理解又最重要的一個參數,它配置的是 AudioTrack 內部的音頻緩沖區的大小,該緩沖區的值不能低於一幀“音頻幀”(Frame)的大小,而前一篇文章介紹過,一幀音頻幀的大小計算如下:
int size = 采樣率 x 位寬 x 采樣時間 x 通道數
采樣時間一般取 2.5ms~120ms 之間,由廠商或者具體的應用決定,我們其實可以推斷,每一幀的采樣時間取得越短,產生的延時就應該會越小,當然,碎片化的數據也就會越多。
在Android開發中,AudioTrack 類提供了一個幫助你確定這個 bufferSizeInBytes 的函數,原型如下:
int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat);
不同的廠商的底層實現是不一樣的,但無外乎就是根據上面的計算公式得到一幀的大小,音頻緩沖區的大小則必須是一幀大小的2~N倍,有興趣的朋友可以繼續深入源碼探究探究。
實際開發中,強烈建議由該函數計算出需要傳入的 bufferSizeInBytes,而不是自己手動計算。
(6) mode
AudioTrack 提供了兩種播放模式,一種是 static 方式,一種是 streaming 方式,前者需要一次性將所有的數據都寫入播放緩沖區,簡單高效,通常用於播放鈴聲、系統提醒的音頻片段; 后者則是按照一定的時間間隔不間斷地寫入音頻數據,理論上它可用於任何音頻播放的場景。
可選的值以常量的形式定義在 AudioTrack 類中,一個是 MODE_STATIC,另一個是 MODE_STREAM,根據具體的應用傳入對應的值即可。
4. 示例代碼
我將 AudioTrack 類的接口簡單封裝了一下,提供了一個 AudioPlayer 類,可以到我的Github下載:https://github.com/Jhuster/Android/blob/master/Audio/AudioPlayer.java
這里也貼出來一份:
1 import android.util.Log; 2 import android.media.AudioFormat; 3 import android.media.AudioManager; 4 import android.media.AudioTrack; 5 6 public class AudioPlayer { 7 8 private static final String TAG = "AudioPlayer"; 9 10 private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_MUSIC; 11 private static final int DEFAULT_SAMPLE_RATE = 44100; 12 private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO; 13 private static final int DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; 14 private static final int DEFAULT_PLAY_MODE = AudioTrack.MODE_STREAM; 15 16 private boolean mIsPlayStarted = false; 17 private int mMinBufferSize = 0; 18 private AudioTrack mAudioTrack; 19 20 public boolean startPlayer() { 21 return startPlayer(DEFAULT_STREAM_TYPE,DEFAULT_SAMPLE_RATE,DEFAULT_CHANNEL_CONFIG,DEFAULT_AUDIO_FORMAT); 22 } 23 24 public boolean startPlayer(int streamType, int sampleRateInHz, int channelConfig, int audioFormat) { 25 26 if (mIsPlayStarted) { 27 Log.e(TAG, "Player already started !"); 28 return false; 29 } 30 31 mMinBufferSize = AudioTrack.getMinBufferSize(sampleRateInHz,channelConfig,audioFormat); 32 if (mMinBufferSize == AudioTrack.ERROR_BAD_VALUE) { 33 Log.e(TAG, "Invalid parameter !"); 34 return false; 35 } 36 Log.d(TAG , "getMinBufferSize = "+mMinBufferSize+" bytes !"); 37 38 mAudioTrack = new AudioTrack(streamType,sampleRateInHz,channelConfig,audioFormat,mMinBufferSize,DEFAULT_PLAY_MODE); 39 if (mAudioTrack.getState() == AudioTrack.STATE_UNINITIALIZED) { 40 Log.e(TAG, "AudioTrack initialize fail !"); 41 return false; 42 } 43 44 mIsPlayStarted = true; 45 46 Log.d(TAG, "Start audio player success !"); 47 48 return true; 49 } 50 51 public int getMinBufferSize() { 52 return mMinBufferSize; 53 } 54 55 public void stopPlayer() { 56 57 if (!mIsPlayStarted) { 58 return; 59 } 60 61 if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { 62 mAudioTrack.stop(); 63 } 64 65 mAudioTrack.release(); 66 mIsPlayStarted = false; 67 68 Log.d(TAG, "Stop audio player success !"); 69 } 70 71 public boolean play(byte[] audioData, int offsetInBytes, int sizeInBytes) { 72 73 if (!mIsPlayStarted) { 74 Log.e(TAG, "Player not started !"); 75 return false; 76 } 77 78 if (sizeInBytes < mMinBufferSize) { 79 Log.e(TAG, "audio data is not enough !"); 80 return false; 81 } 82 83 if (mAudioTrack.write(audioData,offsetInBytes,sizeInBytes) != sizeInBytes) { 84 Log.e(TAG, "Could not write all the samples to the audio device !"); 85 } 86 87 mAudioTrack.play(); 88 89 Log.d(TAG , "OK, Played "+sizeInBytes+" bytes !"); 90 91 return true; 92 } 93 }

