使用AudioTrack播放pcm流式音頻

一埂材、什么是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)景野来。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末恼除,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌豁辉,老刑警劉巖令野,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異徽级,居然都是意外死亡气破,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)餐抢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)现使,“玉大人,你說(shuō)我怎么就攤上這事弹澎∑酉拢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵苦蒿,是天一觀(guān)的道長(zhǎng)殴胧。 經(jīng)常有香客問(wèn)我,道長(zhǎng)佩迟,這世上最難降的妖魔是什么团滥? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮报强,結(jié)果婚禮上灸姊,老公的妹妹穿的比我還像新娘。我一直安慰自己秉溉,他們只是感情好力惯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著召嘶,像睡著了一般父晶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上弄跌,一...
    開(kāi)封第一講書(shū)人閱讀 49,821評(píng)論 1 290
  • 那天甲喝,我揣著相機(jī)與錄音,去河邊找鬼铛只。 笑死埠胖,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的淳玩。 我是一名探鬼主播直撤,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蜕着!你這毒婦竟也來(lái)了谊惭?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎圈盔,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體悄雅,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡驱敲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宽闲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片众眨。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖容诬,靈堂內(nèi)的尸體忽然破棺而出娩梨,到底是詐尸還是另有隱情,我是刑警寧澤览徒,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布狈定,位于F島的核電站,受9級(jí)特大地震影響习蓬,放射性物質(zhì)發(fā)生泄漏纽什。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一躲叼、第九天 我趴在偏房一處隱蔽的房頂上張望芦缰。 院中可真熱鬧,春花似錦枫慷、人聲如沸让蕾。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)探孝。三九已至,卻和暖如春神帅,著一層夾襖步出監(jiān)牢的瞬間再姑,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工找御, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留元镀,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓霎桅,卻偏偏與公主長(zhǎng)得像栖疑,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子滔驶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349