Android音视频专题(二) 在 Android 平台使用 AudioRecord 和 AudioTrack API 完成音频 PCM 数据的采集和播放,并实现读写音频 wav 文件
一、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 implementsOnClickListener {private static final String TAG = "AudioTrackPlayerDemoActivity";private Button button;private byte[] audioData;private AudioTrack audioTrack;@Overridepublic 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>() {@Overrideprotected 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;}@Overrideprotected 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語音電話等場景。
四、源碼?
https://github.com/renhui/AudioDemo?
總結
以上是生活随笔為你收集整理的Android音视频专题(二) 在 Android 平台使用 AudioRecord 和 AudioTrack API 完成音频 PCM 数据的采集和播放,并实现读写音频 wav 文件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 1.1 ubuntu环境下搭建gd32v
- 下一篇: android 飞框动画,Android