音頻播放
音頻播放聲音分為MediaPlayer和AudioTrack兩種方案的沟娱。MediaPlayer可以播放多種格式的聲音文件铭污,例如MP3嗡官,WAV箭窜,OGG,AAC衍腥,MIDI等绽快。然而AudioTrack只能播放PCM數(shù)據(jù)流芥丧。當(dāng)然兩者之間還是有緊密的聯(lián)系,MediaPlayer在播放音頻時坊罢,在framework層還是會創(chuàng)建AudioTrack续担,把解碼后的PCM數(shù)流傳遞給AudioTrack,最后由AudioFlinger進(jìn)行混音活孩,傳遞音頻給硬件播放出來物遇。利用AudioTrack播放只是跳過Mediaplayer的解碼部分而已。
AudioTrack作用
AudioTrack是管理和播放單一音頻資源的類憾儒。AudioTrack僅僅能播放已經(jīng)解碼的PCM流询兴,用于PCM音頻流的回放。
AudioTrack實(shí)現(xiàn)PCM音頻播放
AudioTrack實(shí)現(xiàn)PCM音頻播放五步走
- 配置基本參數(shù)
- 獲取最小緩沖區(qū)大小
- 創(chuàng)建AudioTrack對象
- 獲取PCM文件起趾,轉(zhuǎn)成DataInputStream
- 開啟/停止播放
直接上代碼再分析
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
public class AudioTrackManager {
private AudioTrack mAudioTrack;
private DataInputStream mDis;//播放文件的數(shù)據(jù)流
private Thread mRecordThread;
private boolean isStart = false;
private volatile static AudioTrackManager mInstance;
//音頻流類型
private static final int mStreamType = AudioManager.STREAM_MUSIC;
//指定采樣率 (MediaRecoder 的采樣率通常是8000Hz AAC的通常是44100Hz诗舰。 設(shè)置采樣率為44100,目前為常用的采樣率训裆,官方文檔表示這個值可以兼容所有的設(shè)置)
private static final int mSampleRateInHz=44100 ;
//指定捕獲音頻的聲道數(shù)目眶根。在AudioFormat類中指定用于此的常量
private static final int mChannelConfig= AudioFormat.CHANNEL_CONFIGURATION_MONO; //單聲道
//指定音頻量化位數(shù) ,在AudioFormaat類中指定了以下各種可能的常量。通常我們選擇ENCODING_PCM_16BIT和ENCODING_PCM_8BIT PCM代表的是脈沖編碼調(diào)制边琉,它實(shí)際上是原始音頻樣本属百。
//因此可以設(shè)置每個樣本的分辨率為16位或者8位,16位將占用更多的空間和處理能力,表示的音頻也更加接近真實(shí)变姨。
private static final int mAudioFormat=AudioFormat.ENCODING_PCM_16BIT;
//指定緩沖區(qū)大小族扰。調(diào)用AudioRecord類的getMinBufferSize方法可以獲得。
private int mMinBufferSize;
//STREAM的意思是由用戶在應(yīng)用程序通過write方式把數(shù)據(jù)一次一次得寫到audiotrack中定欧。這個和我們在socket中發(fā)送數(shù)據(jù)一樣渔呵,
// 應(yīng)用層從某個地方獲取數(shù)據(jù),例如通過編解碼得到PCM數(shù)據(jù)砍鸠,然后write到audiotrack扩氢。
private static int mMode = AudioTrack.MODE_STREAM;
public AudioTrackManager() {
initData();
}
private void initData(){
//根據(jù)采樣率,采樣精度睦番,單雙聲道來得到frame的大小类茂。
mMinBufferSize = AudioTrack.getMinBufferSize(mSampleRateInHz,mChannelConfig, mAudioFormat);//計(jì)算最小緩沖區(qū)
//注意耍属,按照數(shù)字音頻的知識托嚣,這個算出來的是一秒鐘buffer的大小。
//創(chuàng)建AudioTrack
mAudioTrack = new AudioTrack(mStreamType, mSampleRateInHz,mChannelConfig,
mAudioFormat,mMinBufferSize,mMode);
}
/**
* 獲取單例引用
*
* @return
*/
public static AudioTrackManager getInstance() {
if (mInstance == null) {
synchronized (AudioTrackManager.class) {
if (mInstance == null) {
mInstance = new AudioTrackManager();
}
}
}
return mInstance;
}
/**
* 銷毀線程方法
*/
private void destroyThread() {
try {
isStart = false;
if (null != mRecordThread && Thread.State.RUNNABLE == mRecordThread.getState()) {
try {
Thread.sleep(500);
mRecordThread.interrupt();
} catch (Exception e) {
mRecordThread = null;
}
}
mRecordThread = null;
} catch (Exception e) {
e.printStackTrace();
} finally {
mRecordThread = null;
}
}
/**
* 啟動播放線程
*/
private void startThread() {
destroyThread();
isStart = true;
if (mRecordThread == null) {
mRecordThread = new Thread(recordRunnable);
mRecordThread.start();
}
}
/**
* 播放線程
*/
Runnable recordRunnable = new Runnable() {
@Override
public void run() {
try {
//設(shè)置線程的優(yōu)先級
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
byte[] tempBuffer = new byte[mMinBufferSize];
int readCount = 0;
while (mDis.available() > 0) {
readCount= mDis.read(tempBuffer);
if (readCount == AudioTrack.ERROR_INVALID_OPERATION || readCount == AudioTrack.ERROR_BAD_VALUE) {
continue;
}
if (readCount != 0 && readCount != -1) {//一邊播放一邊寫入語音數(shù)據(jù)
//判斷AudioTrack未初始化厚骗,停止播放的時候釋放了示启,狀態(tài)就為STATE_UNINITIALIZED
if(mAudioTrack.getState() == mAudioTrack.STATE_UNINITIALIZED){
initData();
}
mAudioTrack.play();
mAudioTrack.write(tempBuffer, 0, readCount);
}
}
stopPlay();//播放完就停止播放
} catch (Exception e) {
e.printStackTrace();
}
}
};
/**
* 播放文件
* @param path
* @throws Exception
*/
private void setPath(String path) throws Exception {
File file = new File(path);
mDis = new DataInputStream(new FileInputStream(file));
}
/**
* 啟動播放
*
* @param path
*/
public void startPlay(String path) {
try {
// //AudioTrack未初始化
// if(mAudioTrack.getState() == AudioTrack.STATE_UNINITIALIZED){
// throw new RuntimeException("The AudioTrack is not uninitialized");
// }//AudioRecord.getMinBufferSize的參數(shù)是否支持當(dāng)前的硬件設(shè)備
// else if (AudioTrack.ERROR_BAD_VALUE == mMinBufferSize || AudioTrack.ERROR == mMinBufferSize) {
// throw new RuntimeException("AudioTrack Unable to getMinBufferSize");
// }else{
setPath(path);
startThread();
// }
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 停止播放
*/
public void stopPlay() {
try {
destroyThread();//銷毀線程
if (mAudioTrack != null) {
if (mAudioTrack.getState() == AudioRecord.STATE_INITIALIZED) {//初始化成功
mAudioTrack.stop();//停止播放
}
if (mAudioTrack != null) {
mAudioTrack.release();//釋放audioTrack資源
}
}
if (mDis != null) {
mDis.close();//關(guān)閉數(shù)據(jù)輸入流
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
配置基本參數(shù)
-
StreamType音頻流類型
最主要的幾種STREAM
- AudioManager.STREAM_MUSIC:用于音樂播放的音頻流。
- AudioManager.STREAM_SYSTEM:用于系統(tǒng)聲音的音頻流领舰。
- AudioManager.STREAM_RING:用于電話鈴聲的音頻流夫嗓。
- AudioManager.STREAM_VOICE_CALL:用于電話通話的音頻流迟螺。
- AudioManager.STREAM_ALARM:用于警報的音頻流。
- AudioManager.STREAM_NOTIFICATION:用于通知的音頻流舍咖。
- AudioManager.STREAM_BLUETOOTH_SCO:用于連接到藍(lán)牙電話時的手機(jī)音頻流矩父。
- AudioManager.STREAM_SYSTEM_ENFORCED:在某些國家實(shí)施的系統(tǒng)聲音的音頻流。
- AudioManager.STREAM_DTMF:DTMF音調(diào)的音頻流排霉。
- AudioManager.STREAM_TTS:文本到語音轉(zhuǎn)換(TTS)的音頻流窍株。
為什么分那么多種類型,其實(shí)原因很簡單攻柠,比如你在聽music的時候接到電話球订,這個時候music播放肯定會停止,此時你只能聽到電話瑰钮,如果你調(diào)節(jié)音量的話冒滩,這個調(diào)節(jié)肯定只對電話起作用。當(dāng)電話打完了浪谴,再回到music开睡,你肯定不用再調(diào)節(jié)音量了。
其實(shí)系統(tǒng)將這幾種聲音的數(shù)據(jù)分開管理较店,STREAM參數(shù)對AudioTrack來說士八,它的含義就是告訴系統(tǒng),我現(xiàn)在想使用的是哪種類型的聲音梁呈,這樣系統(tǒng)就可以對應(yīng)管理他們了婚度。
-
MODE模式(static和stream兩種)
-
AudioTrack.MODE_STREAM
STREAM的意思是由用戶在應(yīng)用程序通過write方式把數(shù)據(jù)一次一次得寫到AudioTrack中。這個和我們在socket中發(fā)送數(shù)據(jù)一樣官卡,應(yīng)用層從某個地方獲取數(shù)據(jù)蝗茁,例如通過編解碼得到PCM數(shù)據(jù),然后write到AudioTrack寻咒。這種方式的壞處就是總是在JAVA層和Native層交互哮翘,效率損失較大。
-
AudioTrack.MODE_STATIC
STATIC就是數(shù)據(jù)一次性交付給接收方毛秘。好處是簡單高效饭寺,只需要進(jìn)行一次操作就完成了數(shù)據(jù)的傳遞;缺點(diǎn)當(dāng)然也很明顯,對于數(shù)據(jù)量較大的音頻回放叫挟,顯然它是無法勝任的艰匙,因而通常只用于播放鈴聲、系統(tǒng)提醒等對內(nèi)存小的操作
-
-
采樣率:mSampleRateInHz
采樣率 (MediaRecoder 的采樣率通常是8000Hz AAC的通常是44100Hz抹恳。 設(shè)置采樣率為44100员凝,目前為常用的采樣率,官方文檔表示這個值可以兼容所有的設(shè)置)
-
通道數(shù)目:mChannelConfig
首先得出聲道數(shù)奋献,目前最多只支持雙聲道健霹。為什么最多只支持雙聲道旺上?看下面的源碼
static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) { int channelCount = 0; switch(channelConfig) { case AudioFormat.CHANNEL_OUT_MONO: case AudioFormat.CHANNEL_CONFIGURATION_MONO: channelCount = 1; break; case AudioFormat.CHANNEL_OUT_STEREO: case AudioFormat.CHANNEL_CONFIGURATION_STEREO: channelCount = 2; break; default: if (!isMultichannelConfigSupported(channelConfig)) { loge("getMinBufferSize(): Invalid channel configuration."); return ERROR_BAD_VALUE; } else { channelCount = AudioFormat.channelCountFromOutChannelMask(channelConfig); } } ....... }
-
音頻量化位數(shù):mAudioFormat(只支持8bit和16bit兩種。)
if ((audioFormat !=AudioFormat.ENCODING_PCM_16BIT) && (audioFormat !=AudioFormat.ENCODING_PCM_8BIT)) { returnAudioTrack.ERROR_BAD_VALUE; }
最小緩沖區(qū)大小
mMinBufferSize取決于采樣率糖埋、聲道數(shù)和采樣深度三個屬性,那么具體是如何計(jì)算的呢宣吱?我們看一下源碼
static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {
....
int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat);
if (size <= 0) {
loge("getMinBufferSize(): error querying hardware");
return ERROR;
}
else {
return size;
}
}
看到源碼緩沖區(qū)的大小的實(shí)現(xiàn)在nativen層中,接著看下native層代碼實(shí)現(xiàn):
rameworks/base/core/jni/android_media_AudioTrack.cpp
static jint android_media_AudioTrack_get_min_buff_size(JNIEnv*env, jobject thiz,
jint sampleRateInHertz,jint nbChannels, jint audioFormat) {
int frameCount = 0;
if(AudioTrack::getMinFrameCount(&frameCount, AUDIO_STREAM_DEFAULT,sampleRateInHertz) != NO_ERROR) {
return -1;
}
return frameCount * nbChannels * (audioFormat ==javaAudioTrackFields.PCM16 ? 2 : 1);
}
這里又調(diào)用了getMinFrameCount瞳别,這個函數(shù)用于確定至少需要多少Frame才能保證音頻正常播放凌节。那么Frame代表了什么意思呢?可以想象一下視頻中幀的概念洒试,它代表了某個時間點(diǎn)的一幅圖像倍奢。這里的Frame也是類似的,它應(yīng)該是指某個特定時間點(diǎn)時的音頻數(shù)據(jù)量垒棋,所以android_media_AudioTrack_get_min_buff_size中最后采用的計(jì)算公式就是:
至少需要多少幀每幀數(shù)據(jù)量 = frameCount * nbChannels * (audioFormat ==javaAudioTrackFields.PCM16 ? 2 : 1);
公式中frameCount就是需要的幀數(shù)卒煞,每一幀的數(shù)據(jù)量又等于:
Channel數(shù)每個Channel數(shù)據(jù)量= nbChannels * (audioFormat ==javaAudioTrackFields.PCM16 ? 2 : 1)層層返回getMinBufferSize就得到了保障AudioTrack正常工作的最小緩沖區(qū)大小了。
創(chuàng)建AudioTrack對象
取到mMinBufferSize后叼架,我們就可以創(chuàng)建一個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);
}
在源碼中一層層往下看
public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
int mode, int sessionId)
throws IllegalArgumentException {
super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK);
.....
// native initialization
int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,
sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
mNativeBufferSizeInBytes, mDataLoadMode, session, 0 /*nativeTrackInJavaObj*/);
if (initResult != SUCCESS) {
loge("Error code "+initResult+" when initializing AudioTrack.");
return; // with mState == STATE_UNINITIALIZED
}
mSampleRate = sampleRate[0];
mSessionId = session[0];
if (mDataLoadMode == MODE_STATIC) {
mState = STATE_NO_STATIC_DATA;
} else {
mState = STATE_INITIALIZED;
}
baseRegisterPlayer();
}
最終看到了又在native_setup方法中,在native中initialization乖订,看看實(shí)現(xiàn)些什么了
/*frameworks/base/core/jni/android_media_AudioTrack.cpp*/
static int android_media_AudioTrack_native_setup(JNIEnv*env, jobject thiz, jobject weak_this,
jint streamType, jintsampleRateInHertz, jint javaChannelMask,
jint audioFormat, jintbuffSizeInBytes, jint memoryMode, jintArray jSession)
{
.....
sp<AudioTrack>lpTrack = new AudioTrack();
.....
AudioTrackJniStorage* lpJniStorage =new AudioTrackJniStorage();
這里調(diào)用了native_setup來創(chuàng)建一個本地AudioTrack對象扮饶,創(chuàng)建一個Storage對象,從這個Storage猜測這可能是存儲音頻數(shù)據(jù)的地方乍构,我們再進(jìn)入了解這個Storage對象甜无。
if (memoryMode== javaAudioTrackFields.MODE_STREAM) {
lpTrack->set(
...
audioCallback, //回調(diào)函數(shù)
&(lpJniStorage->mCallbackData),//回調(diào)數(shù)據(jù)
0,
0,//shared mem
true,// thread cancall Java
sessionId);//audio session ID
} else if (memoryMode ==javaAudioTrackFields.MODE_STATIC) {
...
lpTrack->set(
...
audioCallback, &(lpJniStorage->mCallbackData),0,
lpJniStorage->mMemBase,// shared mem
true,// thread cancall Java
sessionId);//audio session ID
}
....// native_setup結(jié)束
調(diào)用set函數(shù)為AudioTrack設(shè)置這些屬性——我們只保留兩種內(nèi)存模式(STATIC和STREAM)有差異的地方,入?yún)⒅械牡箶?shù)第三個是lpJniStorage->mMemBase哥遮,而STREAM類型時為null(0)岂丘。太深了,對于基礎(chǔ)的知識先研究到這里吧
獲取PCM文件眠饮,轉(zhuǎn)成DataInputStream
根據(jù)存放PCM的路徑獲取到PCM文件
/**
* 播放文件
* @param path
* @throws Exception
*/
private void setPath(String path) throws Exception {
File file = new File(path);
mDis = new DataInputStream(new FileInputStream(file));
}
開啟/停止播放
-
開始播放
public void play()throws IllegalStateException { if (mState != STATE_INITIALIZED) { throw new IllegalStateException("play() called on uninitialized AudioTrack."); } //FIXME use lambda to pass startImpl to superclass final int delay = getStartDelayMs(); if (delay == 0) { startImpl(); } else { new Thread() { public void run() { try { Thread.sleep(delay); } catch (InterruptedException e) { e.printStackTrace(); } baseSetStartDelayMs(0); try { startImpl(); } catch (IllegalStateException e) { // fail silently for a state exception when it is happening after // a delayed start, as the player state could have changed between the // call to start() and the execution of startImpl() } } }.start(); } }
-
停止播放
停止播放音頻數(shù)據(jù)奥帘,如果是STREAM模式,會等播放完最后寫入buffer的數(shù)據(jù)才會停止仪召。如果立即停止寨蹋,要調(diào)用pause()方法,然后調(diào)用flush方法扔茅,會舍棄還沒有播放的數(shù)據(jù)已旧。
public void stop()throws IllegalStateException { if (mState != STATE_INITIALIZED) { throw new IllegalStateException("stop() called on uninitialized AudioTrack."); } // stop playing synchronized(mPlayStateLock) { native_stop(); baseStop(); mPlayState = PLAYSTATE_STOPPED; mAvSyncHeader = null; mAvSyncBytesRemaining = 0; } }
-
暫停播放
暫停播放,調(diào)用play()重新開始播放咖摹。
-
釋放本地AudioTrack資源
AudioTrack.release()
-
返回當(dāng)前的播放狀態(tài)
AudioTrack.getPlayState()
注意: flush()只在模式為STREAM下可用评姨。將音頻數(shù)據(jù)刷進(jìn)等待播放的隊(duì)列难述,任何寫入的數(shù)據(jù)如果沒有提交的話萤晴,都會被舍棄吐句,但是并不能保證所有用于數(shù)據(jù)的緩沖空間都可用于后續(xù)的寫入。
總結(jié)
- 播放一個PCM文件店读,按照上面的五步走嗦枢。
- 注意參數(shù)有配置,如量化位數(shù)是8BIT還是16BIT等屯断。
- 想更加了解AudioTrack里的方法就動手寫一個demo深入了解那些方法的用途文虏。
- 能不能續(xù)播(還沒有驗(yàn)證)