Android 音視頻開發(三):使用 AudioTrack 播放PCM音頻


一、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語音電話等場景。

四、源碼 

https://github.com/renhui/AudioDemo 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM