音頻筆記-AudioTrack

AudioTrack是什麼?

官方釋義:管理和播放單個音頻資源。將PCM音頻緩衝區流傳輸到音頻接收器以進行播放。

AudioTrack和其他播放器的區別

播放器 區別/關係
AudioTrack 只能播放PCM(.wav格式的音頻文件)數據流
MediaPlayer 本質是先將.mp3等格式解碼成PCM流,然後底層會調用AudioTrack進行播放
SoundPool 短音頻,播放提示音,按鍵音之類的音頻

AudioTrack的使用流程

1.初始化AudioTrack。
2.將數據從流或者文件中不斷的寫入緩存。(靜態模式會一次性全部寫入緩存)
2.不斷的將音頻流從緩存中拿出來(靜態模式會一次性全部拿出來)
3.將拿到的緩存數據,不斷的播放出來。
4.停止或者銷燬。

使用AudioTrack前的基本概念

1.AudioTrack的兩種傳輸模式(TransferMode):

傳輸模式(TransferMode) 區別 操作
AudioTrack.MODE_STATIC 長(流)音頻,多次讀取,可能存在延遲,需要將數據一次一次拷貝到AudioTrack的緩存中。 先play,然後不停的write。
AudioTrack.MODE_STREAM 短音頻,一次讀完。如果數據過長,可能會直接奔掉,只需將數據一次性全部拷貝到AudioTrack的緩存中。 必須先write 將數據一次加載完成,再play

2.AudioTrack的基本構造

audioTrack = new AudioTrack.Builder()
        .setAudioAttributes(new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build())
        .setAudioFormat(new AudioFormat.Builder()
                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                .setSampleRate(8000)
                .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
                .build())
        .setTransferMode(AudioTrack.MODE_STREAM)
        .setSessionId(0)
        .build();

構造中各種參數的含義

1.setAudioAttributes()

主要用來設置音頻屬性

  • setUsage()
音頻的用途:
比如是要作爲音樂播放,
還是手機導航語音播放,
還是作爲收到消息的提示音使用,等。

如:(還有很多,這裏只列舉3種)
AudioAttributes.USAGE_MEDIA
AudioAttributes.USAGE_ALARM
AudioAttributes.USAGE_VOICE_COMMUNICATION
  • setContentType()
音頻的內容:
比如當前要播放的音頻內容是聊天語音,
還是一段音樂,還是按鍵音效,等。

如:(還有很多,這裏只列舉3種)
AudioAttributes.CONTENT_TYPE_SPEECH
AudioAttributes.CONTENT_TYPE_MUSIC
AudioAttributes.CONTENT_TYPE_MOVIE

設置這些屬性有到底有什麼用?

這取決於Android系統的運行策略。
不知道大夥有沒有這種情況:

正在聽音樂,聽的很high。突然電話來了,你會發現你的音樂聲音會自動變小或者關閉。當你結束電話,音頻又會開始播放。

這是就是setAudioAttributes()所起到的作用。幫你判斷目前音頻的使用環境,並做出相應處理。

2.setAudioFormat()

  • setEncoding(音頻採樣大小)
指一秒內傳輸的數據大小。採樣大小越高聲音越飽滿。

目前只有兩種模式:
ENCODING_PCM_8BIT
ENCODING_PCM_16BIT
  • setSampleRate(採樣率)
指錄音設備在一秒鐘內對聲音信號的採樣次數,採樣率越高聲音越細膩。
我目前項目裏是16000Hz。

官方說44100Hz可以兼容所有值。(目前大部分app用的應該也是44100Hz)
  • setChannelMask(通道數)
通道,通俗點就是耳機的聲道,聽歌的雙聲道,打遊戲的5.1聲道,7.1聲道,就是這個聲道。

AudioFormat.CHANNEL_OUT_MONO
AudioFormat.CHANNEL_OUT_STEREO
AudioFormat.CHANNEL_OUT_7POINT1_SURROUND

3.setTransferMode()

傳輸模式,上面講到的 靜態 和 流式。

AudioTrack.MODE_STREAM
AudioTrack.MODE_STATIC

4.setBufferSizeInBytes(最小緩存區大小)

還有個概念,就是minBufferSize,它是幹嘛的?

應用層能分配多大的數據Buffer。也就是保障AudioTrack正常工作的最小緩衝區大小。

也就是播放當前音頻每一幀需要的緩存大小。

它的計算方式

AudioTrack.getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)

舉例:
int minBufferSize = AudioTrack.getMinBufferSize(8000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);

也就是上面構造中setAudioFormat()方法裏的三個參數。
構造中有.setBufferSizeInBytes()這個方法,就是用來設置這個值的。

3.AudioTrack的其他構造

- new AudioTrack.Builder().setAudioAttributes().setAudioFormat().setBufferSizeInBytes().setTransferMode().build();	[最推薦的用法]

- new AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int mode, int sessionId);	[和Builder類似,但是需要自行調用getMinBufferSize計算一下緩存大小]

- new AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode)	[早期API3-9的方法,官方已不推薦使用]

4.開始播放音頻

不管哪種構造方法,播放還是一樣的。

播放前的判空

audioTrack == null	//是否爲空
audioTrack.getState() != AudioTrack.STATE_INITIALIZED	//是否已經初始化
audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) //是否正在播放(這一條根據自己業務需求而定)

初始化狀態 和 播放狀態

1.初始化狀態

AudioTrack.STATE_UNINITIALIZED		未初始化(這個時候直接播放,會奔潰)
AudioTrack.STATE_INITIALIZED		已初始化
AudioTrack.STATE_NO_STATIC_DATA		靜態播放流已加載

2.播放狀態

AudioTrack.PLAYSTATE_STOPPED	播放停止
AudioTrack.PLAYSTATE_PAUSED		播放暫停
AudioTrack.PLAYSTATE_PLAYING	播放中

靜態播放

先說靜態播放,前面說了,一次性全部讀取。

public void staticPlay() {

        //這裏以raw資源文件爲例:因爲是短資源,所以直接放在資源文件中了。
        
        byte[] staticData = new byte[0];
        InputStream in = getResources().openRawResource(R.raw.msg);

        try {
            int sizeOfInputStram  = in.available();
            staticData = new byte[sizeOfInputStram];
            Log.i("ddd", "文件大小:" + sizeOfInputStram);
            ByteArrayOutputStream out = new ByteArrayOutputStream(sizeOfInputStram);

//            for (int b; (b = in.read()) != -1; ) {
//                out.write(b);
//            }

            int rc;
            while ((rc = in.read(staticData, 0, sizeOfInputStram)) != -1) {
                out.write(staticData, 0, rc);
            }

            staticData = out.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        staticAudioTrack = new AudioTrack.Builder()
                .setAudioAttributes(new AudioAttributes.Builder()
                        .setUsage(AudioAttributes.USAGE_MEDIA)
                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                        .setLegacyStreamType(AudioManager.STREAM_MUSIC)
                        .build())
                .setAudioFormat(new AudioFormat.Builder()
                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                        .setSampleRate(staticData.length)//由於短音頻一般會在一秒內播完,所以推薦使用資源的大小作爲參數
                        .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
                        .build())
                .setBufferSizeInBytes(staticData.length)//這裏設置資源的長度,因爲是靜態的短音頻
                .setTransferMode(AudioTrack.MODE_STATIC)//這裏是靜態模式
                .setSessionId(0)
                .build();

        Log.i("ddd", "靜態大小:" + staticData.length);

        //一次性寫入AudioTrack
        staticAudioTrack.write(staticData, 0, staticData.length);
        //播放
        staticAudioTrack.play();


    }

流式播放

先開始播放,再不斷的讀取


//這裏以讀取file文件爲例,因爲文件較大,放在資源文件中不太合理。

//流式初始化
audioTrack = new AudioTrack.Builder()
        .setAudioAttributes(new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build())
        .setAudioFormat(new AudioFormat.Builder()
                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                .setSampleRate(44100)//根據實際項目而定
                .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
                .build())
        .setTransferMode(AudioTrack.MODE_STREAM)//流式模式
        .setSessionId(0)
        .build();

//計算一次讀取的大小
int minBufferSize = AudioTrack.getMinBufferSize(8000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);

//這裏以讀取一個長文件爲例

file = new File("文件路徑");
fileInputStream = new FileInputStream(file);
dataInputStream = new DataInputStream(new BufferedInputStream(fileInputStream))
byte[] bytes = new byte[minBufferSize];//(這裏的bytes就是上面通過getMinBufferSize計算出來的長度值)
int len;
audioTrack.play();
while ((len = dataInputStream.read(bytes)) != -1) {
    //每次讀取minBufferSize的長度
    audioTrack.write(bytes, 0, len);
}
audioTrack.stop();//這裏在播放完成時調用stop

audioTrack.release()

釋放資源。
當不需要或者使用完audioTrack時,可調用此方法釋放掉資源和佔用你的內存。
因爲這個方法調用後,audioTrack狀態將回到 未初始化 狀態,需要重新調用構造函數。
所以,還需要使用時,無需調用此方法。只需stop停止即可。當確定不再使用(比如退出當前頁面,這個音頻只是播放一次)時,可調用此方法釋放掉。

調用此方法後最好再置空。

audioTrack = null//將audioTrak置空

Demo地址

https://github.com/tc7326/audio.video.demo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章