天天看點

Android音視訊開發 — AudioTrack的使用

AudioTrack的使用

    • 簡介
    • 使用
    • 測試

簡介

AudioTrack類為Java應用程式管理和播放單個音頻資源。它允許PCM音頻緩沖區流到音頻接收器進行播放。

AudioTrack有兩個模式

模式 解釋 作用範圍
靜态模式(MODE_STATIC) 這種模式下,在play之前隻需要把所有資料通過一次write調用傳遞到AudioTrack中的内部緩沖區 在處理适合記憶體的短聲音以及需要以盡可能小的延遲播放時,應選擇靜态模式
流模式(MODE_STREAM) 以流的形式持續把音頻資料寫到AudioTrack内部的Buffer中 由于音頻資料的特性(高采樣率、每采樣位數…)太大而無法放入記憶體的 等等

使用

1、加上讀取檔案的權限

2、因用到系統的檔案做

耗時操作

是以需要用到AsyncTask

3、最主要的是

public int write

(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes)方法

第一個參數是 儲存要播放的資料的數組

第二個參數是 在音頻資料中,以位元組表示的偏移量,其中要寫入的資料 (我也不是清楚,希望有大佬能解釋一下,建議寫0就好)

第三個參數是 寫入音頻資料的位元組數

4、具體解釋請看下面的代碼

package com.audioandvideo.two.Activity;

import androidx.appcompat.app.AppCompatActivity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.audioandvideo.R;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

/**
 * MODE_STATIC:這種模式下,在play之前隻需要把所有資料通過一次write調用傳遞到AudioTrack中的内部緩沖區
 * MODE_STREAM:以流的形式持續把音頻資料寫到AudioTrack内部的Buffer中
 */
public class AudioTrackActivity extends AppCompatActivity implements View.OnClickListener{
    private Button btnStatic;
    private Button btnStream;
    private AudioTrack audioTrack;
    private byte[] audioData;

    // 采樣率:音頻的采樣頻率,每秒鐘能夠采樣的次數,采樣率越高,音質越高
    // 44100是目前的标準,但是某些裝置仍然支援22050,16000,11025
    // 采樣頻率一般共分為22.05KHz、44.1KHz、48KHz三個等級
    private final int AUDIO_SAMPLE_RATE = 44100;

    // 聲道設定:android支援雙聲道立體聲和單聲道。MONO單聲道,STEREO立體聲
    private final  int AUDIO_CHANNEL = AudioFormat.CHANNEL_OUT_STEREO;

    // 編碼制式和采樣大小:采集來的資料當然使用PCM編碼
    // (脈沖代碼調制編碼,即PCM編碼。PCM通過抽樣、量化、編碼三個步驟将連續變化的模拟信号轉換為數字編碼。)
    // android支援的采樣大小16bit 或者8bit。當然采樣大小越大,那麼資訊量越多,音質也越高,現在主流的采樣
    // 大小都是16bit,在低品質的語音傳輸的時候8bit 足夠了。
    private final  int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
    //要播放的pcm檔案的儲存位置及檔案名
    private final String pcmFileName = Environment.getExternalStorageDirectory() + "/Download/record.pcm";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_audio_track);
        btnStatic = findViewById(R.id.btn_static);
        btnStream = findViewById(R.id.btn_stream);
        btnStatic.setOnClickListener(this);
        btnStream.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_static:
                new AsyncStatic().execute();
                break;
            case R.id.btn_stream:
                new AsyncStream().execute();
                break;
        }
    }

    /**
     *MODE_STATIC:這種模式下,在play之前隻需要把所有資料通過一次write調用傳遞到AudioTrack中的内部緩沖區
     */
   private class AsyncStatic extends AsyncTask{
       /**
        * 背景執行,比較耗時的操作都可以放在這裡。注意這裡不能直接操作UI。
        * 此方法在背景線程執行,完成任務的主要工作,通常需要較長的時間。
        * @param objects
        * @return
        */
       @Override
       protected Object doInBackground(Object[] objects) {
           try {
               InputStream in = new FileInputStream(new File(pcmFileName));
               try {
         //位元組數組輸出流在記憶體中建立一個位元組數組緩沖區,所有發送到輸出流的資料儲存在該位元組數組緩沖區中。
                   ByteArrayOutputStream out = new ByteArrayOutputStream();
                   for (int b; (b = in.read()) != -1; ) {
                       out.write(b);
                   }
                   //建立一個新配置設定的位元組數組。數組的大小和目前輸出流的大小,内容是目前輸出流的拷貝。
                   audioData = out.toByteArray();
               } finally {
                   //最後關閉檔案
                   in.close();
               }
           } catch (IOException e) {
           }
           return null;
       }

       /**
        * 相當于Handler 處理UI的方式,在這裡面可以使用在doInBackground
        * 得到的結果處理操作UI。 此方法在主線程執行,任務執行的結果作為此方法的參數傳回
        * @param o
        */
       @Override
       protected void onPostExecute(Object o) {

           audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, AUDIO_SAMPLE_RATE,
                   AUDIO_CHANNEL, AUDIO_ENCODING, audioData.length,AudioTrack.MODE_STATIC);
           audioTrack.write(audioData, 0, audioData.length); //将音頻資料寫入音頻接收器以便播放
           audioTrack.play();//開始播放
       }
   }

    /**
     * MODE_STREAM:以流的形式持續把音頻資料寫到AudioTrack内部的Buffer中
     */
   private class AsyncStream extends AsyncTask{

       @Override
       protected Object doInBackground(Object[] objects) {
           final int minBufferSize = AudioTrack.getMinBufferSize(AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING);
           audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, AUDIO_SAMPLE_RATE,
                  AUDIO_CHANNEL, AUDIO_ENCODING, minBufferSize, AudioTrack.MODE_STREAM);//最後一個參數為audioTrack的模式
           audioTrack.play();

           File file = new File(pcmFileName);
           //從檔案系統中的某個檔案中獲得輸入位元組
           FileInputStream fileInputStream = null;
           try {
               fileInputStream = new FileInputStream(file);
           } catch (FileNotFoundException e) {
               e.printStackTrace();
           }
           byte[] tempBuffer = new byte[minBufferSize];
           while (true) {
               try {
                   if (!(fileInputStream.available() > 0)) {
                       stop();            //停止播放
                       break;
                   }
//  fileInputStream.read(tempBuffer)傳回讀入緩沖區的總位元組數;如果因為已經到達流末尾而不再有資料可用,則傳回 -1。
                   int readCount = fileInputStream.read(tempBuffer);
                   Log.d("TAG", "doInBackground:"+String.valueOf(readCount));
                   if (readCount == AudioTrack.ERROR_INVALID_OPERATION ||
                           readCount == AudioTrack.ERROR_BAD_VALUE) {
                       continue;
                   }
                   if (readCount != 0 && readCount != -1) {
                       //将音頻資料寫入音頻接收器以便播放
                       audioTrack.write(tempBuffer, 0, readCount);
                   }
               } catch (IOException e) {
                   e.printStackTrace();
               }

           }
           return null;
       }
   }
//停止播放
   private void stop(){
       if(audioTrack.getState() != AudioTrack.STATE_UNINITIALIZED){
           audioTrack.stop();
           audioTrack.release();
       }
   }
}
           

測試

界面隻有兩個簡單的按鈕進行測試,均能播放。

Android音視訊開發 — AudioTrack的使用