Android 音視頻開發(二):使用 AudioRecord 采集音頻PCM並保存到文件


版權聲明:轉載請說明出處:http://www.cnblogs.com/renhui/p/7457321.html

一、AudioRecord API詳解

AudioRecord是Android系統提供的用於實現錄音的功能類。

要想了解這個類的具體的說明和用法,我們可以去看一下官方的文檔:

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

  開始錄音的時候,AudioRecord需要初始化一個相關聯的聲音buffer, 這個buffer主要是用來保存新的聲音數據。這個buffer的大小,我們可以在對象構造期間去指定。它表明一個AudioRecord對象還沒有被讀取(同步)聲音數據前能錄多長的音(即一次可以錄制的聲音容量)。聲音數據從音頻硬件中被讀出,數據大小不超過整個錄音數據的大小(可以分多次讀出),即每次讀取初始化buffer容量的數據。

實現Android錄音的流程為:

  1. 構造一個AudioRecord對象,其中需要的最小錄音緩存buffer大小可以通過getMinBufferSize方法得到。如果buffer容量過小,將導致對象構造的失敗。
  2. 初始化一個buffer,該buffer大於等於AudioRecord對象用於寫聲音數據的buffer大小。
  3. 開始錄音
  4. 創建一個數據流,一邊從AudioRecord中讀取聲音數據到初始化的buffer,一邊將buffer中數據導入數據流。
  5. 關閉數據流
  6. 停止錄音

二、使用 AudioRecord 實現錄音,並生成wav

2.1  創建一個AudioRecord對象

首先要聲明一些全局的變量參數:

private AudioRecord audioRecord = null;  // 聲明 AudioRecord 對象
private int recordBufSize = 0; // 聲明recoordBufffer的大小字段

獲取buffer的大小並創建AudioRecord:

public void createAudioRecord() {
  recordBufSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, EncodingBitRate);  //audioRecord能接受的最小的buffer大小
   audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, EncodingBitRate, recordBufSize);
}

2.2  初始化一個buffer

byte data[] = new byte[recordBufSize]; 

2.3  開始錄音

audioRecord.startRecording();

isRecording = true;

2.4  創建一個數據流,一邊從AudioRecord中讀取聲音數據到初始化的buffer,一邊將buffer中數據導入數據流。

FileOutputStream os = null;

try {
    os = new FileOutputStream(filename);
} catch (FileNotFoundException e) {
    e.printStackTrace();
}

if (null != os) {
while (isRecording) {
read = audioRecord.read(data, 0, recordBufSize);
     // 如果讀取音頻數據沒有出現錯誤,就將數據寫入到文件
if (AudioRecord.ERROR_INVALID_OPERATION != read) {
try {
os.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}

try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
} 

2.5 關閉數據流

修改標志位:isRecording 為false,上面的while循環就自動停止了,數據流也就停止流動了,Stream也就被關閉了。

isRecording = false;

2.6 停止錄音

停止錄音之后,注意要釋放資源。

if (null != audioRecord) {
  audioRecord.stop();
   audioRecord.release();
  audioRecord = null;
   recordingThread = null;
}

注:權限需求:WRITE_EXTERNAL_STORAGE、RECORD_AUDIO 

到現在基本的錄音的流程就介紹完了。但是這時候,有人就提出問題來了:

1)、我按照流程,把音頻數據都輸出到文件里面了,停止錄音后,打開此文件,發現不能播放,到底是為什么呢?

答:按照流程走完了,數據是進去了,但是現在的文件里面的內容僅僅是最原始的音頻數據,術語稱為raw(中文解釋是“原材料”或“未經處理的東西”),這時候,你讓播放器去打開,它既不知道保存的格式是什么,又不知道如何進行解碼操作。當然播放不了。

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

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

添加WAVE文件頭的代碼如下:

public class PcmToWavUtil {

    /**
     * 緩存的音頻大小
     */
    private int mBufferSize;
    /**
     * 采樣率
     */
    private int mSampleRate;
    /**
     * 聲道數
     */
    private int mChannel;


    /**
     * @param sampleRate sample rate、采樣率
     * @param channel channel、聲道
     * @param encoding Audio data format、音頻格式
     */
    PcmToWavUtil(int sampleRate, int channel, int encoding) {
        this.mSampleRate = sampleRate;
        this.mChannel = channel;
        this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, encoding);
    }


    /**
     * pcm文件轉wav文件
     *
     * @param inFilename 源文件路徑
     * @param outFilename 目標文件路徑
     */
    public void pcmToWav(String inFilename, String outFilename) {
        FileInputStream in;
        FileOutputStream out;
        long totalAudioLen;
        long totalDataLen;
        long longSampleRate = mSampleRate;
        int channels = mChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 2;
        long byteRate = 16 * mSampleRate * channels / 8;
        byte[] data = new byte[mBufferSize];
        try {
            in = new FileInputStream(inFilename);
            out = new FileOutputStream(outFilename);
            totalAudioLen = in.getChannel().size();
            totalDataLen = totalAudioLen + 36;

            writeWaveFileHeader(out, totalAudioLen, totalDataLen,
                longSampleRate, channels, byteRate);
            while (in.read(data) != -1) {
                out.write(data);
            }
            in.close();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 加入wav文件頭
     */
    private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
                                     long totalDataLen, long longSampleRate, int channels, long byteRate)
        throws IOException {
        byte[] header = new byte[44];
        // RIFF/WAVE header
        header[0] = 'R';
        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);
        //WAVE
        header[8] = 'W';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        // 'fmt ' chunk
        header[12] = 'f';
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        // 4 bytes: size of 'fmt ' chunk
        header[16] = 16;
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        // format = 1
        header[20] = 1;
        header[21] = 0;
        header[22] = (byte) channels;
        header[23] = 0;
        header[24] = (byte) (longSampleRate & 0xff);
        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
        header[27] = (byte) ((longSampleRate >> 24) & 0xff);
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        // block align
        header[32] = (byte) (2 * 16 / 8);
        header[33] = 0;
        // bits per sample
        header[34] = 16;
        header[35] = 0;
        //data
        header[36] = 'd';
        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, 0, 44);
    }
}

三、附言

Android SDK 提供了兩套音頻采集的API,分別是:MediaRecorder 和 AudioRecord,前者是一個更加上層一點的API,它可以直接把手機麥克風錄入的音頻數據進行編碼壓縮(如AMR、MP3等)並存成文件,而后者則更接近底層,能夠更加自由靈活地控制,可以得到原始的一幀幀PCM音頻數據。如果想簡單地做一個錄音機,錄制成音頻文件,則推薦使用 MediaRecorder,而如果需要對音頻做進一步的算法處理、或者采用第三方的編碼庫進行壓縮、以及網絡傳輸等應用,則建議使用 AudioRecord,其實 MediaRecorder 底層也是調用了 AudioRecord 與 Android Framework 層的 AudioFlinger 進行交互的。直播中實時采集音頻自然是要用AudioRecord了。

四、源碼

https://github.com/renhui/AudioDemo

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM