Android音頻開發(fā)之音頻采集(AudioRecord)

版權聲明:本文為衛(wèi)偉學習總結(jié)文章,轉(zhuǎn)載請注明出處!
在Android系統(tǒng)中,一般使用AudioRecord或者MediaRecord來采集音頻。
AudioRecord:是一個比較偏底層的API,它可以獲取到一幀幀PCM數(shù)據(jù),之后可以對這些數(shù)據(jù)進行處理。
MediaRecorder:是基于AudioRecorder的API(最終還是會創(chuàng)建AudioRecord用來與AudioFlinger進行交互),它可以直接將采集到的音頻數(shù)據(jù)轉(zhuǎn)化為執(zhí)行的編碼格式,并保存。
直播技術采用的就是AudioRecorder采集音頻數(shù)據(jù)。
一、基本API
獲取最小的緩沖區(qū)大小,用于AudioRecord采集到的音頻數(shù)據(jù)。

 static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)

audioRecord構(gòu)造方法: 根據(jù)具體的參數(shù)配置,請求硬件資源創(chuàng)建一個可以用于采集音頻的AudioRecord對象。
audioSource:音頻采集的來源
audioSampleRate:音頻采樣率
channelConfig: 聲道
audioFormat: 音頻采樣精度,指定采樣的數(shù)據(jù)的格式和每次采樣的大小
bufferSizeBytes:AudioRecord采集的音頻數(shù)據(jù)所存放的緩沖區(qū)大小。

public Params initAudioDevice() {
    int[] sampleRates = {48000, 44100, 22050, 16000, 11025,8000,4000};
    for (int sampleRate : sampleRates) {
        //編碼制式
        int audioFormat = mConfig.audioFormat;
        // stereo 立體聲,
        int channelConfig = mConfig.channelConfig;
        int buffsize = 2 * AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
        mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, channelConfig,
                audioFormat, buffsize);
        if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
            continue;
        }
        this.buffer = new byte[Math.min(4096, buffsize)];

        return new Params(sampleRate,
                channelConfig == AudioFormat.CHANNEL_CONFIGURATION_STEREO ? 2 : 1);
    }

    return null;
}

開始采集
開始采集之后,狀態(tài)為RECORDSTATE_RECORDING 。

public void startRecording ()

讀取錄制內(nèi)存,將采集到的數(shù)據(jù)讀取到緩沖區(qū)
方法調(diào)用的返回值的狀態(tài)碼:
情況異常:

  • 1.ERROR_INVALID_OPERATION if the object wasn't properly initialized
  • 2.ERROR_BAD_VALUE if the parameters don't resolve to valid data and indexes.

情況正常:the number of bytes that were read

public int read (ByteBuffer audioBuffer, int sizeInBytes)
public int read (byte[] audioData, int offsetInBytes, int sizeInBytes)
public int read (short[] audioData, int offsetInShorts, int sizeInShorts)

獲取AudioRecord的狀態(tài)
用于檢測AudioRecord是否確保了獲取適當?shù)挠布Y源。在AudioRecord對象實例化之后調(diào)用。
STATE_INITIALIZED 初始完畢
STATE_UNINITIALIZED 未初始化

public int getState ()

返回當前AudioRecord的采集狀態(tài)
public static final int RECORDSTATE_STOPPED = 1; 停止狀態(tài)
調(diào)用void stop()之后的狀態(tài)
public static final int RECORDSTATE_RECORDING = 3;正在采集
調(diào)用startRecording()之后的狀態(tài)

public int getRecordingState() 

二、AudioRecord采集音頻的基本流程

  • 權限

    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
  • 構(gòu)造一個AudioRecord對象。

  • 開始采集

  • 讀取采集的數(shù)據(jù)

  • 停止采集

三、Android音頻開發(fā)之音頻采集
構(gòu)造一個AudioRecord對象

AudioRecord audioRecord = new AudioRecord(audioResource, audioSampleRate, channelConfig, audioFormat, bufferSizeInBytes);

獲取bufferSizeLnBytes值
bufferSizeLnBytes是AudioRecord采集的音頻數(shù)據(jù)所存放的緩沖區(qū)大小。
注意:這個大小不能隨便設置,AudioRecord提供對應的API來獲取這個值。

this.bufferSizeInBytes = AudioRecord.getMinBufferSize(audioSampleRate, channelConfig, audioFormat);

通過bufferSizeInBytes返回就可以知道傳入給AudioRecord.getMinBufferSize的參數(shù)是否支持當前的硬件設備。

if (AudioRecord.ERROR_BAD_VALUE == bufferSizeInBytes || AudioRecord.ERROR == bufferSizeInBytes) {
throw new RuntimeException("Unable to getMinBufferSize");
}

//bufferSizeInBytes is available...

開始采集

  • 在開始錄音之前,首先要判斷一下AudioRecord的狀態(tài)是否已經(jīng)初始化完畢了。

    //判斷AudioRecord的狀態(tài)是否初始化完畢
    //在AudioRecord對象構(gòu)造完畢之后,就處于AudioRecord.STATE_INITIALIZED狀態(tài)了。
    int state = audioRecord.getState();
    if (state == AudioRecord.STATE_UNINITIALIZED) {
      throw new RuntimeException("AudioRecord STATE_UNINITIALIZED");
    }
    
  • 開始采集

    audioRecord.startRecording();
    //開啟線程讀取數(shù)據(jù)
    new Thread(recordTask).start();
    
  • 讀取采集的數(shù)據(jù)
    上面提到,AudioRecord在采集數(shù)據(jù)時會將數(shù)據(jù)存放到緩沖區(qū)中,因此我們只需要創(chuàng)建一個數(shù)據(jù)流去從緩沖區(qū)中將采集的數(shù)據(jù)讀取出來即可。
    創(chuàng)建一個數(shù)據(jù)流,一邊從AudioRecord中讀取音頻數(shù)據(jù)到緩沖區(qū),一邊將緩沖區(qū)中數(shù)據(jù)寫入到數(shù)據(jù)流。

因為需要使用IO操作,因此讀取數(shù)據(jù)的過程應該在子線程中執(zhí)行

//創(chuàng)建一個流,存放從AudioRecord讀取的數(shù)據(jù)
File saveFile = new File(Environment.getExternalStorageDirectory(), "audio-record.pcm");
DataOutputStream dataOutputStream = new DataOutputStream(
            new BufferedOutputStream(new FileOutputStream(saveFile)));

private Runnable recordTask = new Runnable() {
@Override
public void run() {
    //設置線程的優(yōu)先級
    android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIOR
    Log.i(TAG, "設置采集音頻線程優(yōu)先級");
    final byte[] data = new byte[bufferSizeInBytes];
    //標記為開始采集狀態(tài)
    isRecording = true;
    Log.i(TAG, "設置當前當前狀態(tài)為采集狀態(tài)");
    //getRecordingState獲取當前AudioReroding是否正在采集數(shù)據(jù)的狀態(tài)
    while (isRecording && audioRecord.getRecordingState() == AudioRecord
        //讀取采集數(shù)據(jù)到緩沖區(qū)中,read就是讀取到的數(shù)據(jù)量
        final int read = audioRecord.read(data, 0, bufferSizeInBytes);
        if (AudioRecord.ERROR_INVALID_OPERATION != read && AudioRecord.E
            //將數(shù)據(jù)寫入到文件中
            dataOutputStream.write(buffer,0,read);
        }
    }
}
};
  • 停止采集

    /**
     * 停止錄音
     */
    public void stopRecord() throws IOException {
       Log.i(TAG, "停止錄音,回收AudioRecord對象,釋放內(nèi)存");
       isRecording = false;
       if (audioRecord != null) {
          if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
             audioRecord.stop();
             Log.i(TAG, "audioRecord.stop()");
            }
      if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
          audioRecord.release();
          Log.i(TAG, "audioRecord.release()");
      }
    }
    

AudioRecord采集PCM音頻原始數(shù)據(jù)完整功能實現(xiàn)代碼:

package com.weiwei.mediarecordertest;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

 /**
  * AudioRecord 錄音采集
  * AudioTrack  實現(xiàn)錄音播放 
 * @author 60116
 *
 */
public class StramActivity extends Activity{
 private Button bt_stream_recorder;
 private TextView tv_stream_msg;
 private ExecutorService mExecutorService;
 private long startRecorderTime, stopRecorderTime;
 private volatile boolean mIsRecording = false;
 private AudioRecord mAudioRecord;
 private FileOutputStream mFileOutputStream;
 private File mAudioRecordFile;
 private byte[] mBuffer;
 //buffer 值不能太大 避免OOM
 private static int BUFFER_SIZE = 2048;
 private boolean mIsPlaying = false;
 private Handler mHandler = new Handler(Looper.getMainLooper());
 
 @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    setTitle("字節(jié)流錄音");
    initView();
    mExecutorService = Executors.newSingleThreadExecutor();
    mBuffer = new byte[BUFFER_SIZE];
}
 
private void initView() {
    bt_stream_recorder = (Button) findViewById(R.id.bt_stream_recorder);
    tv_stream_msg = (TextView) findViewById(R.id.tv_stream_msg);

}

public void recorderaudio(View view) {
    if(mIsRecording) {
         bt_stream_recorder.setText("開始錄音");
         //在開始錄音中如果這個值沒有變false,則一直進行,當再次點擊變false時,錄音才停止
         mIsRecording = false;
    } else {
        bt_stream_recorder.setText("停止錄音");
        //提交后臺任務 執(zhí)行錄音邏輯
        mIsRecording = true;
        //提交后臺任務,執(zhí)行錄音邏輯
        mExecutorService.submit(new Runnable() {

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

/**
 * 開始錄音
 */
private void startRecorder() {
    //realeseRecorder();
    if(!dostart()) recorderFail();
}

 /**
 * 停止錄音
 */
private void stopRecorder() {
    mIsRecording=false;
    if (!doStop()) recorderFail();

}

private boolean dostart() {
    try {
        // 記錄開始錄音時間
        startRecorderTime = System.currentTimeMillis();
        //創(chuàng)建錄音文件
        mAudioRecordFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() +
                 "/recorderdemo/" + System.currentTimeMillis() + ".pcm");
        if (!mAudioRecordFile.getParentFile().exists())
            mAudioRecordFile.getParentFile().mkdirs();
        // 創(chuàng)建文件輸出流
        mFileOutputStream = new FileOutputStream(mAudioRecordFile);
        // 配置AudioRecord
        int audioSource = MediaRecorder.AudioSource.MIC;
        //所有支持android系統(tǒng)都支持
        int sampleRate = 44100;
        // 單聲道輸入
        int channelConfig = AudioFormat.CHANNEL_IN_MONO;
        // pcm_16時所有android系統(tǒng)都支持的
        int autioFormat = AudioFormat.ENCODING_PCM_16BIT;
        //計算AudioRecord內(nèi)部buffer最小
        int minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, autioFormat);
        //buffer不能小于最低要求,也不能小于我們每次我們讀取的大小。
        mAudioRecord = new AudioRecord(audioSource, sampleRate, channelConfig, autioFormat, Math.max(minBufferSize, BUFFER_SIZE));

        //開始錄音
        mAudioRecord.startRecording();
        
        //循環(huán)讀取數(shù)據(jù),寫入輸出流中
        while (mIsRecording) {
             //只要還在錄音就一直讀取
            int read = mAudioRecord.read(mBuffer, 0, BUFFER_SIZE);
            if(read <= 0) {
                return false;
            } else {
                Log.i("Tag8","mBuffer =" +mBuffer.toString());
                mFileOutputStream.write(mBuffer, 0, read);
            }
        }
        
        //退出循環(huán),停止錄音,釋放資源
        stopRecorder();
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    } finally {
        if (mAudioRecord != null) {
            mAudioRecord.release();
        }
    }
    return true;
}

private boolean recorderFail() {
    mHandler.post(new Runnable() {

        @Override
        public void run() {
            bt_stream_recorder.setText("開始錄音");
            tv_stream_msg.setText("錄取失敗,請重新錄入");

            mIsRecording = false;
            Log.i("Tag8", "go here111111111");
        }
    });
    return false;
}

private void realeseRecorder() {
    mAudioRecord.release();
}

private boolean doStop() {
    //停止錄音,關閉文件輸出流
    mAudioRecord.stop();
    mAudioRecord.release();
    mAudioRecord = null;
    Log.i("Tag8", "go here");
    //記錄結(jié)束時間,統(tǒng)計錄音時長
    stopRecorderTime = System.currentTimeMillis();
    //大于3秒算成功,在主線程更新UI
    final int send = (int) (stopRecorderTime - startRecorderTime) / 1000;
    if (send > 3) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                tv_stream_msg.setText("錄音成功:" + send + "秒");
                bt_stream_recorder.setText("開始錄音");
                Log.i("Tag8", "go there");
            }
        });
    } else {
        recorderFail();
        return false;
    }
    return true;
}

/**
 * 播放聲音
 * @param view
 */
public void player(View view) {
    mExecutorService.submit(new Runnable() {

        @Override
        public void run() {
            if (!mIsPlaying) {
                Log.i("Tag8", "go here");
                mIsPlaying = true;
                Log.i("Tag8","mAudioRecordFile =" +mAudioRecordFile.toString());
                doPlay(mAudioRecordFile);
            }
        }
    });
}

private void doPlay(File audioFile) {
    if(audioFile != null) {
        Log.i("Tag8","go there");
        //配置播放器
        //音樂類型,揚聲器播放
        int streamType = AudioManager.STREAM_MUSIC;
        //錄音時采用的采樣頻率,所有播放時同樣的采樣頻率
        int sampleRate = 44100;
        //單聲道,和錄音時設置的一樣
        int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
        // 錄音使用16bit,所有播放時同樣采用該方式
        int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
        //流模式
        int mode = AudioTrack.MODE_STREAM;
        
        //計算最小buffer大小
        int minBufferSize=AudioTrack.getMinBufferSize(sampleRate,channelConfig,audioFormat);
        
        //構(gòu)造AudioTrack  不能小于AudioTrack的最低要求,也不能小于我們每次讀的大小
        AudioTrack audioTrack=new AudioTrack(streamType,sampleRate,channelConfig,audioFormat,
                Math.max(minBufferSize,BUFFER_SIZE),mode);
        audioTrack.setVolume(16f);
        //從文件流讀數(shù)據(jù)
        FileInputStream inputStream = null;
        
        try {
            //循環(huán)讀數(shù)據(jù),寫到播放器去播放
            inputStream = new FileInputStream(audioFile);
            
            //循環(huán)讀數(shù)據(jù),寫到播放器去播放
            int read;
            //只要沒讀完,循環(huán)播放
            while((read=inputStream.read(mBuffer)) > 0) {
                Log.i("Tag8","read:"+read);
                int ret = audioTrack.write(mBuffer, 0, read);
                Log.i("Tag8","ret ==="+ret);
                //檢查write的返回值,處理錯誤
                switch(ret) {
                case AudioTrack.ERROR_INVALID_OPERATION:
                case AudioTrack.ERROR_BAD_VALUE:
                case AudioManager.ERROR_DEAD_OBJECT:
                    playFail();
                    return;
                default:
                    break;
                }
            }
            audioTrack.play();
            Log.i("Tag8","播放成功。。。。");
        } catch (Exception e) {
            e.printStackTrace();
            Log.i("Tag8","e =" +e.toString());
            //讀取失敗
            playFail();
        } finally {
            mIsPlaying = false;
            Log.i("Tag8","播放完畢");
             //關閉文件輸入流
            if(inputStream !=null){
                closeStream(inputStream);
            }
            //播放器釋放
            resetQuietly(audioTrack);
        }
    }
}

private void closeStream(FileInputStream inputStream) {
    try {
        inputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private void resetQuietly(AudioTrack audioTrack) {
    try {
        audioTrack.stop();
        audioTrack.release();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * 播放失敗
 */
private void playFail() {
    mHandler.post(new Runnable() {
        @Override
        public void run() {
            tv_stream_msg.setText("播放失敗");
        }
    });
}

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mExecutorService != null) {
        mExecutorService.shutdownNow();
    }
    if (mAudioRecord != null) {
        mAudioRecord.stop();
        mAudioRecord.release();
        mAudioRecord = null;
    }
}

獲取到的PCM格式的音頻文件

用Audacity播放器播放PCM原始數(shù)據(jù)也能聽見聲音。
注意:

  • 采集數(shù)據(jù)之后,保存的文件為audio-record.pcm,這個文件并不能使用普通的播放器播放。它是一個原始的文件,沒有任何播放格式,因此就無法被播放器識別并播放。
  • 上面的問題可以有兩種解決方法
  • 使用AudioTrack播放pcm格式的音頻數(shù)據(jù)。
  • 將pcm數(shù)據(jù)轉(zhuǎn)化為wav格式數(shù)據(jù),這樣就可以被播放器識別啦。
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容