音視頻開發【3】-android-AudioRecord錄製PCM音頻

android平臺上錄製音頻主要有兩種方式,MediaRecorder、AudioRecord。java

  • MediaRecorder 封裝的層次比較高,能夠直接將手機麥克風錄入的音頻數據進行編碼壓縮並存儲,生成如AMR、MP3等音頻文件。
  • AudioRecord 接近底層,錄製的數據是PCM格式的原始音頻裸數據,能夠對其作進一步的算法處理、編碼壓縮等應用,可以自由進行開發控制。

音頻開發應用場景不少,僅僅錄製保存知足不了大部分實際需求,因此,掌握 AudioRecord 的使用是必須的,本文針對AudioRecord的使用進行總結。android

AudioRecord 的使用主要有如下幾個步驟:算法

  1. 初始化AudioRecord
  2. 初始化音頻數據buffer
  3. 開啓採集
  4. 啓動新線程,從音頻數據buffer中讀取音頻數據
  5. 中止採集、釋放資源

初始化AudioRecord數組

public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes) 複製代碼
  • audioSource:音頻源,在類 MediaRecorder.AudioSource 中以常量的形式定義,經常使用的有DEFAULT(默認),VOICE_RECOGNITION(用於語音識別,等同於DEFAULT),MIC(由手機麥克風輸入),VOICE_COMMUNICATION(用於VoIP應用)等。
  • sampleRateInHz:音頻採樣率,目前44100Hz是惟一能夠保證兼容全部Android手機的採樣率。
  • channelConfig:音頻通道數,在類AudioFormat中以常量的形式定義,經常使用的是 CHANNEL_IN_MONO(單通道),CHANNEL_IN_STEREO(雙通道),注意,前者可以兼容全部android手機。
  • audioFormat:音頻數據位寬(量化精度),在類AudioFormat中以常量的形式定義,經常使用的是 ENCODING_PCM_16BIT(16bit),ENCODING_PCM_8BIT(8bit),注意,前者是能夠保證兼容全部Android手機。
  • bufferSizeInBytes:AudioRecord內部音頻緩衝區大小,不能低於一幀音頻的大小,而一幀音頻的大小(int size = 採樣率 x 位寬 x 採樣時間 x 通道數)與音頻採樣時間有關,通常取 2.5ms~120ms 之間,不一樣廠商取值不一樣,因此AudioRecord類提供了 int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) 函數來獲取改值,實際開發中,強烈建議由該函數計算出須要傳入的 bufferSizeInBytes,而不是本身手動計算。雖然不一樣的廠商的底層實現是不同的,但無外乎就是根據上面的計算公式獲得一幀的大小,音頻緩衝區的大小則必須是一幀大小的2~N倍。

初始化音頻數據bufferide

經過前面獲取的 bufferSizeInBytes ,初始化讀取音頻數據的buffer。函數

final byte[] data = new byte[minBufferSize];
複製代碼

開啓採集編碼

調用 AudioRecord 實例的 startRecording() 方法,開始採集音頻spa

特別注意:開始採集後,要儘快經過音頻讀取數據取走音頻,一旦緩衝區中的數據大於前面設置的bufferSizeInBytes,會發生 over-running線程

啓動新線程,從音頻數據buffer中讀取音頻數據code

開啓新線程,調用 AudioRecord 實例的 public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes) 方法,獲取音頻數據。

中止採集、釋放資源

調用 AudioRecord 實例的 stop()release() 方法,將改實例置爲 null。

示例代碼

別忘了配錄音權限

public class MainActivity extends AppCompatActivity {
    //採樣率,如今可以保證在全部設備上使用的採樣率是44100Hz, 可是其餘的採樣率(22050, 16000, 11025)在一些設備上也可使用。
    public static final int SAMPLE_RATE_INHZ = 44100;
    //聲道數。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是能夠保證在全部設備可以使用的。
    public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
    //返回的音頻數據的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT.
    public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;

    Button btnBegin;
    Button btnEnd;

    private AudioRecord audioRecord;
    private boolean isRecording;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnBegin = (Button) findViewById(R.id.btnBegin);
        btnEnd = (Button) findViewById(R.id.btnEnd);

        btnBegin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                beginRecord();
            }
        });

        btnEnd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                endRecorde();
            }
        });
    }

    private void beginRecord() {
        final int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT);
        audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT, minBufferSize);
        final byte[] data = new byte[minBufferSize];
        audioRecord.startRecording();
        isRecording = true;

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (isRecording) {
                    int read = audioRecord.read(data, 0, minBufferSize);
                    if (read != AudioRecord.ERROR_INVALID_OPERATION) {
                        //處理音頻數據 data
                    }
                }
            }
        }).start();
    }

    private void endRecorde() {
        isRecording = false;
        if (audioRecord != null) {
            audioRecord.stop();
            audioRecord.release();
            audioRecord = null;
        }
    }
}
複製代碼

封裝

參考網上相關資源,這裏給出一個簡單的AudioRecord錄音的封裝類:AudioRecorder。

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log;

/** * Created by wangyt on 2018/10/26 */
public class AudioRecorder {

    private static final String TAG = "AudioRecorder";

    public interface OnAudioDataArrivedListener {
        void onAudioDataArrived(byte[] audioData);
    }

    //聲源
    private static final int DEFFAULT_AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;
    //採樣率,如今可以保證在全部設備上使用的採樣率是44100Hz, 可是其餘的採樣率(22050, 16000, 11025)在一些設備上也可使用。
    private static final int DEFAULT_SAMPLE_RATE_INHZ = 44100;
    //聲道數。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是能夠保證在全部設備可以使用的。
    private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
    //返回的音頻數據的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT.
    private static final int DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
    //內部緩衝區大小
    private int minBufferSize = 0;
    //是否已啓動錄音
    private boolean isStarted = false;
    //是否能夠從緩衝區中讀取數據
    private boolean canReadDataFromBuffer = true;
    //從緩衝區中讀取數據的回調方法
    private OnAudioDataArrivedListener onAudioDataArrivedListener;

    private AudioRecord audioRecord;


    public boolean startRecord() {
        return startRecord(DEFFAULT_AUDIO_SOURCE,
                DEFAULT_SAMPLE_RATE_INHZ,
                DEFAULT_CHANNEL_CONFIG,
                DEFAULT_AUDIO_FORMAT);
    }

    public boolean startRecord(int audioSource, int sampleRate, int channel, int audioFormat) {
        if (isStarted) {
            Log.e(TAG, "startRecord: AudioRecorder has been already started");
            return false;
        }

        //獲取內部緩衝區最小size
        minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channel, audioFormat);
        if (minBufferSize == AudioRecord.ERROR_BAD_VALUE) {
            Log.e(TAG, "startRecord: minBufferSize is error_bad_value");
            return false;
        }
        Log.d(TAG, "startRecord: minBufferSize = " + minBufferSize + "bytes");

        //初始化 audioRecord
        audioRecord = new AudioRecord(audioSource, sampleRate, channel, audioFormat, minBufferSize);
        if (audioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
            Log.e(TAG, "startRecord: audioRecord is uninitialized");
            return false;
        }

        //啓動錄製
        audioRecord.startRecording();

        //能夠從內部緩衝區中讀取數據
        canReadDataFromBuffer = true;

        //啓動子線程
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (canReadDataFromBuffer){
                    //初始化緩衝區數據接收數組
                    byte[] data = new byte[minBufferSize];

                    //讀取內部緩衝區中讀取數據
                    int result = audioRecord.read(data, 0, minBufferSize);

                    if (result == AudioRecord.ERROR_BAD_VALUE){
                        Log.e(TAG, "run: audioRecord.read result is ERROR_BAD_VALUE");
                    }else if (result == AudioRecord.ERROR_INVALID_OPERATION){
                        Log.e(TAG, "run: audioRecord.read result is ERROR_INVALID_OPERATION");
                    }else {
                        if (onAudioDataArrivedListener != null){
                            //調用讀取數據回調方法
                            onAudioDataArrivedListener.onAudioDataArrived(data);
                        }
                        Log.d(TAG, "run: audioRecord read " + result + "bytes");
                    }
                }
            }
        }).start();

        //設置錄音已啓動
        isStarted = true;
        Log.d(TAG, "startRecord: audioRecorder has been already started");
        return true;
    }

    public void stopRecord(){
        //若是錄音還沒有啓動,直接返回
        if (!isStarted) return;
        //設置內部緩衝區數據不可讀取
        canReadDataFromBuffer = false;
        //中止錄音
        if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING){
            audioRecord.stop();
        }
        //釋放資源
        audioRecord.release();
        //設置錄音未啓動
        isStarted = false;
        //回調置爲空
        onAudioDataArrivedListener = null;
    }
}
複製代碼
相關文章
相關標籤/搜索