音視頻入門之音頻採集、編碼、播放

本文由玉剛說寫做平臺提供寫做贊助,版權歸玉剛說微信公衆號全部
原做者:蒼溟
版權聲明:未經玉剛說許可,不得以任何形式轉載java

今天咱們學習音頻的採集、編碼、生成文件、轉碼等操做,咱們生成三種格式的文件格式,pcm、wav、aac 三種格式,而且咱們用 AudioStack 來播放音頻,最後咱們播放這個音頻。android

本篇文章你將學到:ios

  • AudioRecord 音頻的採集
  • 生成 PCM 文件
  • PCM 轉化爲 WAV 文件
  • PCM 轉化爲 AAC 文件
  • 附上正常運行 demo 源碼

使用 AudioRecord 實現錄音生成PCM 文件

AudioRecord 是 Android 系統提供的用於實現錄音的功能類,要想了解這個類的具體的說明和用法,咱們能夠去看一下官方的文檔:git

  AndioRecord類的主要功能是讓各類 Java 應用可以管理音頻資源,以便它們經過此類可以錄製聲音相關的硬件所收集的聲音。此功能的實現就是經過」pulling」(讀取)AudioRecord對象的聲音數據來完成的。在錄音過程當中,應用所須要作的就是經過後面三個類方法中的一個去及時地獲取AudioRecord對象的錄音數據. AudioRecord類提供的三個獲取聲音數據的方法分別是read(byte[], int, int), read(short[], int, int), read(ByteBuffer, int). 不管選擇使用那一個方法都必須事先設定方便用戶的聲音數據的存儲格式。github

  開始錄音的時候,AudioRecord須要初始化一個相關聯的聲音buffer, 這個buffer主要是用來保存新的聲音數據。這個buffer的大小,咱們能夠在對象構造期間去指定。它代表一個AudioRecord對象尚未被讀取(同步)聲音數據前能錄多長的音(即一次能夠錄製的聲音容量)。聲音數據從音頻硬件中被讀出,數據大小不超過整個錄音數據的大小(能夠分屢次讀出),即每次讀取初始化buffer容量的數據。微信

1.1 首先要聲明一些全局的變量和常量參數

主要是聲明一些用到的參數,具體解釋能夠看註釋。app

//指定音頻源 這個和MediaRecorder是相同的 MediaRecorder.AudioSource.MIC指的是麥克風
private static final int mAudioSource = MediaRecorder.AudioSource.MIC;
//指定採樣率 (MediaRecoder 的採樣率一般是8000Hz AAC的一般是44100Hz。 設置採樣率爲44100,目前爲經常使用的採樣率,官方文檔表示這個值能夠兼容全部的設置)
private static final int mSampleRateInHz = 44100;
//指定捕獲音頻的聲道數目。在AudioFormat類中指定用於此的常量,單聲道
private static final int mChannelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO; 
//指定音頻量化位數 ,在AudioFormaat類中指定了如下各類可能的常量。一般咱們選擇ENCODING_PCM_16BIT和ENCODING_PCM_8BIT PCM表明的是脈衝編碼調製,它其實是原始音頻樣本。
//所以能夠設置每一個樣本的分辨率爲16位或者8位,16位將佔用更多的空間和處理能力,表示的音頻也更加接近真實。
private static final int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
//指定緩衝區大小。調用AudioRecord類的getMinBufferSize方法能夠得到。
private int mBufferSizeInBytes;

// 聲明 AudioRecord 對象
private AudioRecord mAudioRecord = null;
複製代碼

1.2 獲取buffer的大小並建立AudioRecord

//初始化數據,計算最小緩衝區
mBufferSizeInBytes = AudioRecord.getMinBufferSize(mSampleRateInHz, mChannelConfig, mAudioFormat);
//建立AudioRecorder對象
mAudioRecord = new AudioRecord(mAudioSource, mSampleRateInHz, mChannelConfig,
                               mAudioFormat, mBufferSizeInBytes);
複製代碼

1.3 建立一個子線程開啓線程錄音,並寫入文件文件

 @Override
    public void run() {
        //標記爲開始採集狀態
        isRecording = true;
        //建立文件
        createFile();

        try {

            //判斷AudioRecord未初始化,中止錄音的時候釋放了,狀態就爲STATE_UNINITIALIZED
            if (mAudioRecord.getState() == mAudioRecord.STATE_UNINITIALIZED) {
                initData();
            }

            //最小緩衝區
            byte[] buffer = new byte[mBufferSizeInBytes];
            //獲取到文件的數據流
            mDataOutputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(mRecordingFile)));

            //開始錄音
            mAudioRecord.startRecording();
            //getRecordingState獲取當前AudioReroding是否正在採集數據的狀態
            while (isRecording && mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
                int bufferReadResult = mAudioRecord.read(buffer, 0, mBufferSizeInBytes);
                for (int i = 0; i < bufferReadResult; i++) {
                    mDataOutputStream.write(buffer[i]);
                }
            }
        } catch (Exception e) {
            Log.e(TAG, "Recording Failed");
        } finally {
            // 中止錄音
            stopRecord();
            IOUtil.close(mDataOutputStream);
        }
    }
複製代碼

1.4 權限和採集小結

注意:權限需求:WRITE_EXTERNAL_STORAGE、RECORD_AUDIO socket

到如今基本的錄音的流程就介紹完了,可是這時候問題來了:ide

1) 我按照流程,把音頻數據都輸出到文件裏面了,中止錄音後,打開此文件,發現不能播放,究竟是爲何呢?函數

答:按照流程走完了,數據是進去了,可是如今的文件裏面的內容僅僅是最原始的音頻數據,術語稱爲raw(中文解釋是「原材料」或「未經處理的東西」),這時候,你讓播放器去打開,它既不知道保存的格式是什麼,又不知道如何進行解碼操做。固然播放不了。

2) 那如何才能在播放器中播放我錄製的內容呢?

答: 在文件的數據開頭加入AAC HEAD 或者 AAC 數據便可,也就是文件頭。只有加上文件頭部的數據,播放器才能正確的知道里面的內容究竟是什麼,進而可以正常的解析並播放裏面的內容。

PCM 、WAV、AAC 的文件頭介紹

我這裏簡單的介紹一下這三種的格式的基本介紹,具體我添加了具體的訪問連接,具體點擊詳情查看,我這裏點到爲止。

PCM:PCM(Pulse Code Modulation----脈碼調製錄音)。所謂PCM錄音就是將聲音等模擬信號變成符號化的脈衝列,再予以記錄。PCM信號是由[1]、[0]等符號構成的數字信號,而未通過任何編碼和壓縮處理。與模擬信號比,它不易受傳送系統的雜波及失真的影響。動態範圍寬,可獲得音質至關好的影響效果。

WAV : wav是一種無損的音頻文件格式,WAV符合 PIFF(Resource Interchange File Format)規範。全部的WAV都有一個文件頭,這個文件頭音頻流的編碼參數。WAV對音頻流的編碼沒有硬性規定,除了PCM以外,還有幾乎全部支持ACM規範的編碼均可覺得WAV的音頻流進行編碼。

簡單來講:WAV 是一種無損的音頻文件格式,PCM是沒有壓縮的編碼方式

AAC : AAC(Advanced Audio Coding),中文稱爲「高級音頻編碼」,出現於1997年,基於 MPEG-2的音頻編碼技術。由Fraunhofer IIS、杜比實驗室、AT&T、Sony(索尼)等公司共同開發,目的是取代MP3格式。2000年,MPEG-4標準出現後,AAC 從新集成了其特性,加入了SBR技術和PS技術,爲了區別於傳統的 MPEG-2 AAC 又稱爲 MPEG-4 AAC。他是一種專爲聲音數據設計的文件壓縮格式,與Mp3相似。利用AAC格式,可以使聲音文件明顯減少,而不會讓人感受聲音質量有所下降 。

PCM 轉化爲 WAV

在文件的數據開頭加入WAVE HEAD 或者 AAC 數據便可,也就是文件頭。只有加上文件頭部的數據,播放器才能正確的知道里面的內容究竟是什麼,進而可以正常的解析並播放裏面的內容。具體的頭文件的描述,在Play a WAV file on an AudioTrack裏面能夠進行了解。

public class WAVUtil {

    /**
     * PCM文件轉WAV文件
     *
     * @param inPcmFilePath  輸入PCM文件路徑
     * @param outWavFilePath 輸出WAV文件路徑
     * @param sampleRate     採樣率,例如44100
     * @param channels       聲道數 單聲道:1或雙聲道:2
     * @param bitNum         採樣位數,8或16
     */

    public static void convertPcm2Wav(String inPcmFilePath, String outWavFilePath, int sampleRate,int channels, int bitNum) {

        FileInputStream in = null;
        FileOutputStream out = null;
        byte[] data = new byte[1024];

        try {
            //採樣字節byte率
            long byteRate = sampleRate * channels * bitNum / 8;

            in = new FileInputStream(inPcmFilePath);
            out = new FileOutputStream(outWavFilePath);

            //PCM文件大小
            long totalAudioLen = in.getChannel().size();

            //總大小,因爲不包括RIFF和WAV,因此是44 - 8 = 36,在加上PCM文件大小
            long totalDataLen = totalAudioLen + 36;

            writeWaveFileHeader(out, totalAudioLen, totalDataLen, sampleRate, channels, byteRate);

            int length = 0;
            while ((length = in.read(data)) > 0) {
                out.write(data, 0, length);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            IOUtil.close(in,out);
        }
    }


    /**
     * 輸出WAV文件
     *
     * @param out           WAV輸出文件流
     * @param totalAudioLen 整個音頻PCM數據大小
     * @param totalDataLen  整個數據大小
     * @param sampleRate    採樣率
     * @param channels      聲道數
     * @param byteRate      採樣字節byte率
     * @throws IOException
     */

    private static void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,long totalDataLen, int sampleRate, int channels, long byteRate) throws IOException {
        byte[] header = new byte[44];
        header[0] = 'R'// RIFF
        header[1] = 'I';
        header[2] = 'F';
        header[3] = 'F';
        header[4] = (byte) (totalDataLen & 0xff);//數據大小
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        header[8] = 'W';//WAVE
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        //FMT Chunk
        header[12] = 'f'// 'fmt '
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';//過渡字節
        //數據大小
        header[16] = 16// 4 bytes: size of 'fmt ' chunk
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        //編碼方式 10H爲PCM編碼格式
        header[20] = 1// format = 1
        header[21] = 0;
        //通道數
        header[22] = (byte) channels;
        header[23] = 0;
        //採樣率,每一個通道的播放速度
        header[24] = (byte) (sampleRate & 0xff);
        header[25] = (byte) ((sampleRate >> 8) & 0xff);
        header[26] = (byte) ((sampleRate >> 16) & 0xff);
        header[27] = (byte) ((sampleRate >> 24) & 0xff);
        //音頻數據傳送速率,採樣率*通道數*採樣深度/8
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        // 肯定系統一次要處理多少個這樣字節的數據,肯定緩衝區,通道數*採樣位數
        header[32] = (byte) (channels * 16 / 8);
        header[33] = 0;
        //每一個樣本的數據位數
        header[34] = 16;
        header[35] = 0;
        //Data chunk
        header[36] = 'd';//data
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (totalAudioLen & 0xff);
        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
        out.write(header, 044);
    }
}
複製代碼

看到下圖咱們生成了相對的 wav 文件,咱們用用本機自帶播放器打開此時就能正常播放,可是咱們發現他的大小比較大,咱們看到就是幾分鐘就這麼大,咱們平時用的是 mp3 、aac 格式的,咱們如何辦到的呢,這裏咱們繼續看一下 mp3 格式如何能生成 。

PCM 轉化爲 AAC 文件格式

生成 aac 文件播放

public class AACUtil {

    ...

    /**
     * 初始化AAC編碼器
     */

    private void initAACMediaEncode() {
        try {

            //參數對應-> mime type、採樣率、聲道數
            MediaFormat encodeFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 160001);
            encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, 64000);//比特率
            encodeFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
            encodeFormat.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO);
            encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
            encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 1024);//做用於inputBuffer的大小

            mediaEncode = MediaCodec.createEncoderByType(encodeType);
            mediaEncode.configure(encodeFormat, nullnull, MediaCodec.CONFIGURE_FLAG_ENCODE);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (mediaEncode == null) {
            LogUtil.e("create mediaEncode failed");
            return;
        }

        mediaEncode.start();
        encodeInputBuffers = mediaEncode.getInputBuffers();
        encodeOutputBuffers = mediaEncode.getOutputBuffers();
        encodeBufferInfo = new MediaCodec.BufferInfo();
    }

    private boolean codeOver = false;

    /**
     * 開始轉碼
     * 音頻數據{@link #srcPath}先解碼成PCM  PCM數據在編碼成MediaFormat.MIMETYPE_AUDIO_AAC音頻格式
     * mp3->PCM->aac
     */

    public void startAsync() {
        LogUtil.w("start");
        new Thread(new DecodeRunnable()).start();
    }


    /**
     * 解碼{@link #srcPath}音頻文件 獲得PCM數據塊
     *
     * @return 是否解碼完全部數據
     */

    private void srcAudioFormatToPCM() {
        File file = new File(srcPath);// 指定要讀取的文件
        FileInputStream fio = null;
        try {
            fio = new FileInputStream(file);
            byte[] bb = new byte[1024];
            while (!codeOver) {
                if (fio.read(bb) != -1) {
                    LogUtil.e("============   putPCMData ============" + bb.length);
                    dstAudioFormatFromPCM(bb);
                } else {
                    codeOver = true;
                }
            }

            fio.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    private byte[] chunkAudio = new byte[0];

    /**
     * 編碼PCM數據 獲得AAC格式的音頻文件
     */

    private void dstAudioFormatFromPCM(byte[] pcmData) {

        int inputIndex;
        ByteBuffer inputBuffer;
        int outputIndex;
        ByteBuffer outputBuffer;

        int outBitSize;
        int outPacketSize;
        byte[] PCMAudio;
        PCMAudio = pcmData;

        encodeInputBuffers = mediaEncode.getInputBuffers();
        encodeOutputBuffers = mediaEncode.getOutputBuffers();
        encodeBufferInfo = new MediaCodec.BufferInfo();


        inputIndex = mediaEncode.dequeueInputBuffer(0);
        inputBuffer = encodeInputBuffers[inputIndex];
        inputBuffer.clear();
        inputBuffer.limit(PCMAudio.length);
        inputBuffer.put(PCMAudio);//PCM數據填充給inputBuffer
        mediaEncode.queueInputBuffer(inputIndex, 0, PCMAudio.length, 00);//通知編碼器 編碼


        outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 0);

        while (outputIndex > 0) {

            outBitSize = encodeBufferInfo.size;
            outPacketSize = outBitSize + 7;//7爲ADT頭部的大小
            outputBuffer = encodeOutputBuffers[outputIndex];//拿到輸出Buffer
            outputBuffer.position(encodeBufferInfo.offset);
            outputBuffer.limit(encodeBufferInfo.offset + outBitSize);
            chunkAudio = new byte[outPacketSize];
            addADTStoPacket(chunkAudio, outPacketSize);//添加ADTS
            outputBuffer.get(chunkAudio, 7, outBitSize);//將編碼獲得的AAC數據 取出到byte[]中

            try {
                //錄製aac音頻文件,保存在手機內存中
                bos.write(chunkAudio, 0, chunkAudio.length);
                bos.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

            outputBuffer.position(encodeBufferInfo.offset);
            mediaEncode.releaseOutputBuffer(outputIndex, false);
            outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 0);

        }

    }

    /**
     * 添加ADTS頭
     *
     * @param packet
     * @param packetLen
     */

    private void addADTStoPacket(byte[] packet, int packetLen) {
        int profile = 2// AAC LC
        int freqIdx = 8// 16KHz
        int chanCfg = 1// CPE

        // fill in ADTS data
        packet[0] = (byte0xFF;
        packet[1] = (byte0xF1;
        packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
        packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
        packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
        packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
        packet[6] = (byte0xFC;

    }

    /**
     * 釋放資源
     */

    public void release() {
      ...
    }

    /**
     * 解碼線程
     */

    private class DecodeRunnable implements Runnable {

        @Override
        public void run() {
            srcAudioFormatToPCM();
        }
    }

}
複製代碼

AudioStack 播放

AudioTrack 類能夠完成Android平臺上音頻數據的輸出任務。AudioTrack有兩種數據加載模式(MODE_STREAM和MODE_STATIC),對應的是數據加載模式和音頻流類型, 對應着兩種徹底不一樣的使用場景。

  • MODE_STREAM:在這種模式下,經過write一次次把音頻數據寫到AudioTrack中。這和平時經過write系統調用往文件中寫數據相似,但這種工做方式每次都須要把數據從用戶提供的Buffer中拷貝到AudioTrack內部的Buffer中,這在必定程度上會使引入延時。爲解決這一問題,AudioTrack就引入了第二種模式。
  • MODE_STATIC:這種模式下,在play以前只須要把全部數據經過一次write調用傳遞到AudioTrack中的內部緩衝區,後續就沒必要再傳遞數據了。這種模式適用於像鈴聲這種內存佔用量較小,延時要求較高的文件。但它也有一個缺點,就是一次write的數據不能太多,不然系統沒法分配足夠的內存來存儲所有數據。

播放聲音能夠用MediaPlayer和AudioTrack,二者都提供了Java API供應用開發者使用。雖然均可以播放聲音,但二者仍是有很大的區別的,其中最大的區別是MediaPlayer能夠播放多種格式的聲音文件,例如MP3,AAC,WAV,OGG,MIDI等。MediaPlayer會在framework層建立對應的音頻解碼器。而AudioTrack只能播放已經解碼的PCM流,若是對比支持的文件格式的話則是AudioTrack只支持wav格式的音頻文件,由於wav格式的音頻文件大部分都是PCM流。AudioTrack不建立解碼器,因此只能播放不須要解碼的wav文件。

3.1 音頻流的類型

在AudioTrack構造函數中,會接觸到AudioManager.STREAM_MUSIC這個參數。它的含義與Android系統對音頻流的管理和分類有關。

Android將系統的聲音分爲好幾種流類型,下面是幾個常見的:

· STREAM_ALARM:警告聲

· STREAM_MUSIC:音樂聲,例如music等

· STREAM_RING:鈴聲

· STREAM_SYSTEM:系統聲音,例如低電提示音,鎖屏音等

· STREAM_VOCIE_CALL:通話聲

注意:上面這些類型的劃分和音頻數據自己並無關係。例如MUSIC和RING類型均可以是某首MP3歌曲。另外,聲音流類型的選擇沒有固定的標準,例如,鈴聲預覽中的鈴聲能夠設置爲MUSIC類型。音頻流類型的劃分和Audio系統對音頻的管理策略有關。

3.2 Buffer分配和Frame的概念

在計算Buffer分配的大小的時候,咱們常常用到的一個方法就是:getMinBufferSize。這個函數決定了應用層分配多大的數據Buffer。

AudioTrack.getMinBufferSize(8000,//每秒8K個採樣點                              
        AudioFormat.CHANNEL_CONFIGURATION_STEREO,//雙聲道                  
        AudioFormat.ENCODING_PCM_16BIT);
複製代碼

從AudioTrack.getMinBufferSize開始追溯代碼,能夠發如今底層的代碼中有一個很重要的概念:Frame(幀)。Frame是一個單位,用來描述數據量的多少。1單位的Frame等於1個採樣點的字節數×聲道數(好比PCM16,雙聲道的1個Frame等於2×2=4字節)。1個採樣點只針對一個聲道,而實際上可能會有一或多個聲道。因爲不能用一個獨立的單位來表示所有聲道一次採樣的數據量,也就引出了Frame的概念。Frame的大小,就是一個採樣點的字節數×聲道數。另外,在目前的聲卡驅動程序中,其內部緩衝區也是採用Frame做爲單位來分配和管理的。

getMinBufSize會綜合考慮硬件的狀況(諸如是否支持採樣率,硬件自己的延遲狀況等)後,得出一個最小緩衝區的大小。通常咱們分配的緩衝大小會是它的整數倍。

3.3 構建過程

每個音頻流對應着一個AudioTrack類的一個實例,每一個AudioTrack會在建立時註冊到 AudioFlinger中,由AudioFlinger把全部的AudioTrack進行混合(Mixer),而後輸送到AudioHardware中進行播放,目前Android同時最多能夠建立32個音頻流,也就是說,Mixer最多會同時處理32個AudioTrack的數據流。

3.4 Show Me The Code

public class AudioTrackManager {
    ...
    //音頻流類型
    private static final int mStreamType = AudioManager.STREAM_MUSIC;
    //指定採樣率 (MediaRecoder 的採樣率一般是8000Hz AAC的一般是44100Hz。 設置採樣率爲44100,目前爲經常使用的採樣率,官方文檔表示這個值能夠兼容全部的設置)
    private static final int mSampleRateInHz = 44100;
    //指定捕獲音頻的聲道數目。在AudioFormat類中指定用於此的常量
    private static final int mChannelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO; //單聲道
    //指定音頻量化位數 ,在AudioFormaat類中指定了如下各類可能的常量。一般咱們選擇ENCODING_PCM_16BIT和ENCODING_PCM_8BIT PCM表明的是脈衝編碼調製,它其實是原始音頻樣本。
    //所以能夠設置每一個樣本的分辨率爲16位或者8位,16位將佔用更多的空間和處理能力,表示的音頻也更加接近真實。
    private static final int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
    //指定緩衝區大小。調用AudioRecord類的getMinBufferSize方法能夠得到。
    private int mMinBufferSize;
    //STREAM的意思是由用戶在應用程序經過write方式把數據一次一次得寫到audiotrack中。這個和咱們在socket中發送數據同樣,
    // 應用層從某個地方獲取數據,例如經過編解碼獲得PCM數據,而後write到audiotrack。
    private static int mMode = AudioTrack.MODE_STREAM;

    private void initData() {
        //根據採樣率,採樣精度,單雙聲道來獲得frame的大小。
        mMinBufferSize = AudioTrack.getMinBufferSize(mSampleRateInHz, mChannelConfig, mAudioFormat);//計算最小緩衝區
        //注意,按照數字音頻的知識,這個算出來的是一秒鐘buffer的大小。
        //建立AudioTrack
        mAudioTrack = new AudioTrack(mStreamType, mSampleRateInHz, mChannelConfig,
                mAudioFormat, mMinBufferSize, mMode);
    }


    /**
     * 啓動播放線程
     */

    private void startThread() {
        destroyThread();
        isStart = true;
        if (mRecordThread == null) {
            mRecordThread = new Thread(recordRunnable);
            mRecordThread.start();
        }
    }

    /**
     * 播放線程
     */

    private Runnable recordRunnable = new Runnable() {
        @Override
        public void run() {
            try {
                //設置線程的優先級
             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) {
                        //判斷AudioTrack未初始化,中止播放的時候釋放了,狀態就爲STATE_UNINITIALIZED
                        if (mAudioTrack.getState() == mAudioTrack.STATE_UNINITIALIZED) {
                            initData();
                        }
                        mAudioTrack.play();
                        mAudioTrack.write(tempBuffer, 0, readCount);
                    }
                }
                //播放完就中止播放
                stopPlay();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    };

    /**
     * 啓動播放
     *
     * @param path
     */

    public void startPlay(String path) {
        try {
            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();//關閉數據輸入流
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
複製代碼

源碼地址

Github 源碼地址 (audio 包下) :welcome fork or star

相關文章
相關標籤/搜索