一埂材、什么是AudioTrack
/**
* The AudioTrack class manages and plays a single audio resource for Java applications.
* It allows streaming of PCM audio buffers to the audio sink for playback. This is
* achieved by "pushing" the data to the AudioTrack object using one of the
* {@link #write(byte[], int, int)}, {@link #write(short[], int, int)},
* and {@link #write(float[], int, int, int)} methods.
*
* <p>An AudioTrack instance can operate under two modes: static or streaming.<br>
* In Streaming mode, the application writes a continuous stream of data to the AudioTrack, using
* one of the {@code write()} methods. These are blocking and return when the data has been
* transferred from the Java layer to the native layer and queued for playback. The streaming
* mode is most useful when playing blocks of audio data that for instance are:
*
* <ul>
* <li>too big to fit in memory because of the duration of the sound to play,</li>
* <li>too big to fit in memory because of the characteristics of the audio data
* (high sampling rate, bits per sample ...)</li>
* <li>received or generated while previously queued audio is playing.</li>
* </ul>
*
* The static mode should be chosen when dealing with short sounds that fit in memory and
* that need to be played with the smallest latency possible. The static mode will
* therefore be preferred for UI and game sounds that are played often, and with the
* smallest overhead possible.
*
* <p>Upon creation, an AudioTrack object initializes its associated audio buffer.
* The size of this buffer, specified during the construction, determines how long an AudioTrack
* can play before running out of data.<br>
* For an AudioTrack using the static mode, this size is the maximum size of the sound that can
* be played from it.<br>
* For the streaming mode, data will be written to the audio sink in chunks of
* sizes less than or equal to the total buffer size.
*
* AudioTrack is not final and thus permits subclasses, but such use is not recommended.
*/
二痒玩、AudioTrack 構(gòu)造方法參數(shù)介紹
public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,
int bufferSizeInBytes, int mode)
throws IllegalArgumentException {
this(streamType, sampleRateInHz, channelConfig, audioFormat,
bufferSizeInBytes, mode, AudioManager.AUDIO_SESSION_ID_GENERATE);
}
streamType: Android將系統(tǒng)的聲音分為好幾種流類(lèi)型愤估,下面是幾個(gè)常見(jiàn)的:
· STREAM_ALARM:警告聲
· STREAM_MUSIC:音樂(lè)聲嵌牺,例如music等
· STREAM_RING:鈴聲
· STREAM_SYSTEM:系統(tǒng)聲音打洼,例如低電提示音,鎖屏音等
· STREAM_VOCIE_CALL:通話(huà)聲
sampleRateInHz: 采樣率 (MediaRecoder 的采樣率通常是8000Hz AAC的通常是44100Hz逆粹。
* 設(shè)置采樣率為44100募疮,目前為常用的采樣率,官方文檔表示這個(gè)值可以兼容所有的設(shè)置)
channelConfig:指定捕獲音頻的聲道數(shù)目僻弹。在AudioFormat類(lèi)中指定用于此的常量
audioFormat :指定音頻量化位數(shù) ,在AudioFormaat類(lèi)中指定了以下各種可能的常量阿浓。
* 通常我們選擇ENCODING_PCM_16BIT和ENCODING_PCM_8BIT PCM代表的是脈沖編碼調(diào)制,它實(shí)際上是原始音頻樣本蹋绽。
* 因此可以設(shè)置每個(gè)樣本的分辨率為16位或者8位芭毙,16位將占用更多的空間和處理能力,表示的音頻也更加接近真實(shí)筋蓖。
bufferSizeInBytes:指定緩沖區(qū)大小,調(diào)用AudioTrack類(lèi)的getMinBufferSize方法可以獲得稿蹲,
mode : AudioTrack有兩種數(shù)據(jù)加載模式(MODE_STREAM和MODE_STATIC)
MODE_STREAM:在這種模式下扭勉,通過(guò)write一次次把音頻數(shù)據(jù)寫(xiě)到AudioTrack中。這和平時(shí)通過(guò)write系統(tǒng)調(diào)用往文件中寫(xiě)數(shù)據(jù)類(lèi)似苛聘,但這種工作方式每次都需要把數(shù)據(jù)從用戶(hù)提供的Buffer中拷貝到AudioTrack內(nèi)部的Buffer中涂炎,這在一定程度上會(huì)使引入延時(shí)。為解決這一問(wèn)題设哗,AudioTrack就引入了第二種模式唱捣。
MODE_STATIC:這種模式下,在play之前只需要把所有數(shù)據(jù)通過(guò)一次write調(diào)用傳遞到AudioTrack中的內(nèi)部緩沖區(qū)网梢,后續(xù)就不必再傳遞數(shù)據(jù)了震缭。這種模式適用于像鈴聲這種內(nèi)存占用量較小,延時(shí)要求較高的文件战虏。但它也有一個(gè)缺點(diǎn)拣宰,就是一次write的數(shù)據(jù)不能太多,否則系統(tǒng)無(wú)法分配足夠的內(nèi)存來(lái)存儲(chǔ)全部數(shù)據(jù)烦感。
三巡社、AudioTrack 兩種模式使用
3.1、AudioTrack MODE_STATIC 使用
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();
3.2手趣、AudioTrack MODE_STREAM 使用
package com.ubtechinc.cruzr.voice.pcm;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import androidx.annotation.NonNull;
import com.ubtrobot.cruzr.core.log.ELog;
import okio.ByteString;
import static android.media.AudioTrack.PLAYSTATE_PLAYING;
public class AudioTrackManager {
private static final String TAG = "AudioTrackManager";
private AudioTrack mAudioTrack;
private volatile static AudioTrackManager mInstance;
private long bufferCount;
/**
* 音頻流類(lèi)型
*/
private static final int mStreamType = AudioManager.STREAM_MUSIC;
/**
* 指定采樣率 (MediaRecoder 的采樣率通常是8000Hz AAC的通常是44100Hz晌该。
* 設(shè)置采樣率為44100,目前為常用的采樣率绿渣,官方文檔表示這個(gè)值可以兼容所有的設(shè)置)
*/
private static final int mSampleRateInHz = 16000;
/**
* 指定捕獲音頻的聲道數(shù)目朝群。在AudioFormat類(lèi)中指定用于此的常量
*/
private static final int mChannelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO; //單聲道
/**
* 指定音頻量化位數(shù) ,在AudioFormaat類(lèi)中指定了以下各種可能的常量。
* 通常我們選擇ENCODING_PCM_16BIT和ENCODING_PCM_8BIT PCM代表的是脈沖編碼調(diào)制中符,它實(shí)際上是原始音頻樣本姜胖。
* 因此可以設(shè)置每個(gè)樣本的分辨率為16位或者8位,16位將占用更多的空間和處理能力,表示的音頻也更加接近真實(shí)淀散。
*/
private static final int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
/**
* 指定緩沖區(qū)大小谭期。調(diào)用AudioTrack類(lèi)的getMinBufferSize方法可以獲得。
*/
private int mMinBufferSize;
/**
* STREAM的意思是由用戶(hù)在應(yīng)用程序通過(guò)write方式把數(shù)據(jù)一次一次得寫(xiě)到audiotrack中吧凉。
* 這個(gè)和我們?cè)趕ocket中發(fā)送數(shù)據(jù)一樣,
* 應(yīng)用層從某個(gè)地方獲取數(shù)據(jù)踏志,例如通過(guò)編解碼得到PCM數(shù)據(jù)阀捅,然后write到audiotrack。
*/
private static int mMode = AudioTrack.MODE_STREAM;
private IAudioPlayStateListener iAudioPlayStateListener;
private static final int BUFFER_CAPITAL = 10;
/**
* 獲取單例引用
*
* @return
*/
public static AudioTrackManager getInstance() {
if (mInstance == null) {
synchronized (AudioTrackManager.class) {
if (mInstance == null) {
mInstance = new AudioTrackManager();
}
}
}
return mInstance;
}
public AudioTrackManager() {
initAudioTrack();
}
private void initAudioTrack() {
//根據(jù)采樣率针余,采樣精度饲鄙,單雙聲道來(lái)得到frame的大小凄诞。
//計(jì)算最小緩沖區(qū) *10
mMinBufferSize = AudioTrack.getMinBufferSize(mSampleRateInHz, mChannelConfig, mAudioFormat);
ELog.i(TAG, "initAudioTrack: mMinBufferSize: " + mMinBufferSize * BUFFER_CAPITAL + " b");
//注意,按照數(shù)字音頻的知識(shí)忍级,這個(gè)算出來(lái)的是一秒鐘buffer的大小帆谍。
mAudioTrack = new AudioTrack(mStreamType, mSampleRateInHz, mChannelConfig,
mAudioFormat, mMinBufferSize * BUFFER_CAPITAL, mMode);
}
public void addAudioPlayStateListener(IAudioPlayStateListener iAudioPlayStateListener) {
this.iAudioPlayStateListener = iAudioPlayStateListener;
}
public void prepareAudioTrack() {
bufferCount = 0;
ELog.i(TAG, "prepareAudioTrack:------> ");
if (null == mAudioTrack) {
return;
}
if (mAudioTrack.getState() == mAudioTrack.STATE_UNINITIALIZED) {
initAudioTrack();
}
mAudioTrack.play();
if (null != iAudioPlayStateListener) {
iAudioPlayStateListener.onStart();
}
}
public synchronized void write(@NonNull final ByteString bytes) {
if (null != mAudioTrack) {
int byteSize = bytes.size();
bufferCount += byteSize;
int write = mAudioTrack.write(bytes.toByteArray(), 0, bytes.size());
ELog.d(TAG, "write: 接收到數(shù)據(jù) " + byteSize + " b | 已寫(xiě)入 " + bufferCount + " b");
if (write == 0 && null != iAudioPlayStateListener) {
//由于緩存的緣故,會(huì)先把緩存的bytes填滿(mǎn)再播放轴咱,當(dāng)write=0的時(shí)候存在沒(méi)有播完的情況
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(iAudioPlayStateListener!=null){
iAudioPlayStateListener.onStop();
}
}
}
}
public void stopPlay() {
ELog.i(TAG, "stopPlay: ");
if (null == mAudioTrack) {
return;
}
if (null != iAudioPlayStateListener) {
iAudioPlayStateListener.onStop();
}
try {
if (mAudioTrack.getPlayState() == PLAYSTATE_PLAYING) {
mAudioTrack.stop();
}
} catch (IllegalStateException e) {
ELog.e(TAG, "stop: " + e.toString());
e.printStackTrace();
}
}
public void release() {
if (null == mAudioTrack) {
return;
}
ELog.i(TAG, "release: ");
stopPlay();
iAudioPlayStateListener = null;
try {
mAudioTrack.release();
mAudioTrack = null;
} catch (Exception e) {
ELog.e(TAG, "release: " + e.toString());
e.printStackTrace();
}
}
public void setBufferParams(int pcmFileSize) {
//設(shè)置緩沖的大小 為PCM文件大小的10%
ELog.d(TAG, "setFileSize: PCM文件大小為:" + pcmFileSize + " b 最小緩存空間為 " + mMinBufferSize * BUFFER_CAPITAL + " b");
if (pcmFileSize < mMinBufferSize * BUFFER_CAPITAL) {
mAudioTrack = new AudioTrack(mStreamType, mSampleRateInHz, mChannelConfig,
mAudioFormat, mMinBufferSize, mMode);
ELog.d(TAG, "setFileSize: pcmFileSize 文件小于最小緩沖數(shù)據(jù)的10倍汛蝙,修改為默認(rèn)的1倍------>");
} else {
//緩存大小為PCM文件大小的10%,如果小于mMinBufferSize * BUFFER_CAPITAL朴肺,則按默認(rèn)值設(shè)置
int cacheFileSize = (int) (pcmFileSize * 0.1);
int realBufferSize = (cacheFileSize / mMinBufferSize + 1) * mMinBufferSize;
ELog.d(TAG,"計(jì)算得到緩存空間為: "+realBufferSize+" b 最小緩存空間為 " + mMinBufferSize * BUFFER_CAPITAL + " b");
if (realBufferSize < mMinBufferSize * BUFFER_CAPITAL) {
realBufferSize=mMinBufferSize * BUFFER_CAPITAL;
}
mAudioTrack = new AudioTrack(mStreamType, mSampleRateInHz, mChannelConfig,
mAudioFormat, realBufferSize, mMode);
ELog.d(TAG, "setFileSize: 重置緩存空間為: " + realBufferSize + " b | "+realBufferSize/1024+" kb");
}
bufferCount = 0;
}
}
四窖剑、AudioTrack 和 MediaPlayer的對(duì)比
4.1 區(qū)別
- 其中最大的區(qū)別是MediaPlayer可以播放多種格式的聲音文件,例如MP3戈稿,AAC西土,WAV,OGG鞍盗,MIDI等需了。MediaPlayer會(huì)在framework層創(chuàng)建對(duì)應(yīng)的音頻解碼器。而AudioTrack只能播放已經(jīng)解碼的PCM流般甲,如果對(duì)比支持的文件格式的話(huà)則是AudioTrack只支持wav格式的音頻文件肋乍,因?yàn)閣av格式的音頻文件大部分都是PCM流。AudioTrack不創(chuàng)建解碼器欣除,所以只能播放不需要解碼的wav文件住拭。
4.2 聯(lián)系
- MediaPlayer在framework層還是會(huì)創(chuàng)建AudioTrack,把解碼后的PCM數(shù)流傳遞給AudioTrack历帚,AudioTrack再傳遞給AudioFlinger進(jìn)行混音滔岳,然后才傳遞給硬件播放,所以是MediaPlayer包含了AudioTrack。
4.3 SoundPool
- 在接觸Android音頻播放API的時(shí)候挽牢,發(fā)現(xiàn)SoundPool也可以用于播放音頻谱煤。下面是三者的使用場(chǎng)景:MediaPlayer 更加適合在后臺(tái)長(zhǎng)時(shí)間播放本地音樂(lè)文件或者在線(xiàn)的流式資源; SoundPool 則適合播放比較短的音頻片段,比如游戲聲音禽拔、按鍵聲刘离、鈴聲片段等等,它可以同時(shí)播放多個(gè)音頻; 而 AudioTrack 則更接近底層睹栖,提供了非常強(qiáng)大的控制能力硫惕,支持低延遲播放,適合流媒體和VoIP語(yǔ)音電話(huà)等場(chǎng)景野来。