一、AudioTrack 基本使用
AudioTrack 類可以完成Android平台上音頻數據的輸出任務。AudioTrack有兩種數據加載模式(MODE_STREAM和MODE_STATIC),對應的是數據加載模式和音頻流類型, 對應着兩種完全不同的使用場景。
- MODE_STREAM:在這種模式下,通過write一次次把音頻數據寫到AudioTrack中。這和平時通過write系統調用往文件中寫數據類似,但這種工作方式每次都需要把數據從用戶提供的Buffer中拷貝到AudioTrack內部的Buffer中,這在一定程度上會使引入延時。為解決這一問題,AudioTrack就引入了第二種模式。
- MODE_STATIC:這種模式下,在play之前只需要把所有數據通過一次write調用傳遞到AudioTrack中的內部緩沖區,后續就不必再傳遞數據了。這種模式適用於像鈴聲這種內存占用量較小,延時要求較高的文件。但它也有一個缺點,就是一次write的數據不能太多,否則系統無法分配足夠的內存來存儲全部數據。
1.1 MODE_STATIC模式
MODE_STATIC模式輸出音頻的方式如下(注意:如果采用STATIC模式,須先調用write寫數據,然后再調用play。):
public class AudioTrackPlayerDemoActivity extends Activity implements OnClickListener { private static final String TAG = "AudioTrackPlayerDemoActivity"; private Button button; private byte[] audioData; private AudioTrack audioTrack; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); super.setContentView(R.layout.main); this.button = (Button) super.findViewById(R.id.play); this.button.setOnClickListener(this); this.button.setEnabled(false); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { try { InputStream in = getResources().openRawResource(R.raw.ding); try { ByteArrayOutputStream out = new ByteArrayOutputStream( 264848); for (int b; (b = in.read()) != -1;) { out.write(b); } Log.d(TAG, "Got the data"); audioData = out.toByteArray(); } finally { in.close(); } } catch (IOException e) { Log.wtf(TAG, "Failed to read", e); } return null; } @Override protected void onPostExecute(Void v) { Log.d(TAG, "Creating track..."); button.setEnabled(true); Log.d(TAG, "Enabled button"); } }.execute(); } public void onClick(View view) { this.button.setEnabled(false); this.releaseAudioTrack(); this.audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, audioData.length, AudioTrack.MODE_STATIC); Log.d(TAG, "Writing audio data..."); this.audioTrack.write(audioData, 0, audioData.length); Log.d(TAG, "Starting playback"); audioTrack.play(); Log.d(TAG, "Playing"); this.button.setEnabled(true); } private void releaseAudioTrack() { if (this.audioTrack != null) { Log.d(TAG, "Stopping"); audioTrack.stop(); Log.d(TAG, "Releasing"); audioTrack.release(); Log.d(TAG, "Nulling"); } } public void onPause() { super.onPause(); this.releaseAudioTrack(); } }
1.2 MODE_STREAM模式
MODE_STREAM 模式輸出音頻的方式如下:
byte[] tempBuffer = new byte[bufferSize]; int readCount = 0; while (dis.available() > 0) { readCount = dis.read(tempBuffer); if (readCount == AudioTrack.ERROR_INVALID_OPERATION || readCount == AudioTrack.ERROR_BAD_VALUE) { continue; } if (readCount != 0 && readCount != -1) { audioTrack.play(); audioTrack.write(tempBuffer, 0, readCount); } }
二、AudioTrack 詳解
2.1 音頻流的類型
在AudioTrack構造函數中,會接觸到AudioManager.STREAM_MUSIC這個參數。它的含義與Android系統對音頻流的管理和分類有關。
Android將系統的聲音分為好幾種流類型,下面是幾個常見的:
· STREAM_ALARM:警告聲
· STREAM_MUSIC:音樂聲,例如music等
· STREAM_RING:鈴聲
· STREAM_SYSTEM:系統聲音,例如低電提示音,鎖屏音等
· STREAM_VOCIE_CALL:通話聲
注意:上面這些類型的划分和音頻數據本身並沒有關系。例如MUSIC和RING類型都可以是某首MP3歌曲。另外,聲音流類型的選擇沒有固定的標准,例如,鈴聲預覽中的鈴聲可以設置為MUSIC類型。音頻流類型的划分和Audio系統對音頻的管理策略有關。
2.2 Buffer分配和Frame的概念
在計算Buffer分配的大小的時候,我們經常用到的一個方法就是:getMinBufferSize。這個函數決定了應用層分配多大的數據Buffer。
AudioTrack.getMinBufferSize(8000,//每秒8K個采樣點 AudioFormat.CHANNEL_CONFIGURATION_STEREO,//雙聲道 AudioFormat.ENCODING_PCM_16BIT);
從AudioTrack.getMinBufferSize開始追溯代碼,可以發現在底層的代碼中有一個很重要的概念:Frame(幀)。Frame是一個單位,用來描述數據量的多少。1單位的Frame等於1個采樣點的字節數×聲道數(比如PCM16,雙聲道的1個Frame等於2×2=4字節)。1個采樣點只針對一個聲道,而實際上可能會有一或多個聲道。由於不能用一個獨立的單位來表示全部聲道一次采樣的數據量,也就引出了Frame的概念。Frame的大小,就是一個采樣點的字節數×聲道數。另外,在目前的聲卡驅動程序中,其內部緩沖區也是采用Frame作為單位來分配和管理的。
下面是追溯到的native層的方法:
// minBufCount表示緩沖區的最少個數,它以Frame作為單位 uint32_t minBufCount = afLatency / ((1000 *afFrameCount)/afSamplingRate); if(minBufCount < 2) minBufCount = 2;//至少要兩個緩沖 //計算最小幀個數 uint32_tminFrameCount = (afFrameCount*sampleRateInHertz*minBufCount)/afSamplingRate; //下面根據最小的FrameCount計算最小的緩沖大小 intminBuffSize = minFrameCount //計算方法完全符合我們前面關於Frame的介紹 * (audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1) * nbChannels; returnminBuffSize;
getMinBufSize會綜合考慮硬件的情況(諸如是否支持采樣率,硬件本身的延遲情況等)后,得出一個最小緩沖區的大小。一般我們分配的緩沖大小會是它的整數倍。
2.3 AudioTrack構造過程
每一個音頻流對應着一個AudioTrack類的一個實例,每個AudioTrack會在創建時注冊到 AudioFlinger中,由AudioFlinger把所有的AudioTrack進行混合(Mixer),然后輸送到AudioHardware中進行播放,目前Android同時最多可以創建32個音頻流,也就是說,Mixer最多會同時處理32個AudioTrack的數據流。
三、 AudioTrack 與 MediaPlayer 的對比
播放聲音可以用MediaPlayer和AudioTrack,兩者都提供了Java API供應用開發者使用。雖然都可以播放聲音,但兩者還是有很大的區別的。
3.1 區別
其中最大的區別是MediaPlayer可以播放多種格式的聲音文件,例如MP3,AAC,WAV,OGG,MIDI等。MediaPlayer會在framework層創建對應的音頻解碼器。而AudioTrack只能播放已經解碼的PCM流,如果對比支持的文件格式的話則是AudioTrack只支持wav格式的音頻文件,因為wav格式的音頻文件大部分都是PCM流。AudioTrack不創建解碼器,所以只能播放不需要解碼的wav文件。
3.2 聯系
MediaPlayer在framework層還是會創建AudioTrack,把解碼后的PCM數流傳遞給AudioTrack,AudioTrack再傳遞給AudioFlinger進行混音,然后才傳遞給硬件播放,所以是MediaPlayer包含了AudioTrack。
3.3 SoundPool
在接觸Android音頻播放API的時候,發現SoundPool也可以用於播放音頻。下面是三者的使用場景:MediaPlayer 更加適合在后台長時間播放本地音樂文件或者在線的流式資源; SoundPool 則適合播放比較短的音頻片段,比如游戲聲音、按鍵聲、鈴聲片段等等,它可以同時播放多個音頻; 而 AudioTrack 則更接近底層,提供了非常強大的控制能力,支持低延遲播放,適合流媒體和VoIP語音電話等場景。