1. 程式人生 > >android平臺下基於MediaRecorder和AudioRecord實現錄製AAC、PCM音訊資料

android平臺下基於MediaRecorder和AudioRecord實現錄製AAC、PCM音訊資料

音視訊實踐學習

概述

android sdk中提供了兩種方式來實現音訊的採集:MediaRecorderAudioRecord,其中的MediaRecorder處於更上層,它可以對音訊錄製的資料編碼成AMR

,MP3等格式,並存儲為檔案,而AudioRecord則更靈活,因為它可以錄製最原始的PCM流資料,這個在直播中很常見,

android ndk中還有一種是基於OpenSLES的方式來處理音訊,但是對API的版本要求最低為23以上,本文暫時不作說明,後續有時間再更新相關的處理操作

錄製介面

使用不同的方式處理音訊,API肯定有所不同,這裡暫時先抽象一個介面:

public interface IAudioRecorder {


    interface RECORD_STATE {
        int STATE_RECORDING = 0;
        int STATE_SUCCESS = 1;
        int STATE_ERROR = 2;
    }

    /**
     * 初始化
     */
    void initRecorder();

    /**
     * 開始錄製音訊
     */
    int recordStart();

    /**
     * 停止錄製音訊
     */
    void recordStop();

}

基於MediaRecorder

系統都已經給你提供好了的輪子,我們只需要設定幾個引數就可以了:

public class MediaRecordRecorder implements IAudioRecorder {

    /**
     * 基於MediaRecorder錄製音訊
     */
    private MediaRecorder mMediaRecorder;

    /**
     * 是否正在錄製
     */
    private boolean isRecord = false;

    private String filePath;

    public MediaRecordRecorder(String filePath){
        this.filePath = filePath;
    }

    @Override
    public void initRecorder() {
        //例項化MediaRecorder物件
        mMediaRecorder = new MediaRecorder();

        //從麥克風採集聲音資料
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        //設定輸出格式為MP4
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        //設定取樣頻率44100 頻率越高,音質越好,檔案越大
        mMediaRecorder.setAudioSamplingRate(44100);
        //設定聲音資料編碼格式,音訊通用格式是AAC
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        //設定編碼頻率
        mMediaRecorder.setAudioEncodingBitRate(96000);
        //設定輸出檔案
        mMediaRecorder.setOutputFile(filePath);
    }

    @Override
    public int recordStart() {
        //判斷是否有外部儲存裝置sdcard
        if (isRecord) {
            return RECORD_STATE.STATE_RECORDING;
        } else {
            if (mMediaRecorder != null) {
                try {
                    mMediaRecorder.prepare();
                    mMediaRecorder.start();
                    // 讓錄製狀態為true
                    isRecord = true;
                    return RECORD_STATE.STATE_SUCCESS;
                } catch (IOException e) {
                    e.printStackTrace();

                }
            }
            return RECORD_STATE.STATE_ERROR;
        }
    }

    @Override
    public void recordStop() {
        if (mMediaRecorder != null) {
            isRecord = false;
            mMediaRecorder.stop();
            mMediaRecorder.release();
            mMediaRecorder = null;
        }
    }

}

筆者這裡只是作演示操作,因此部分引數直接硬編碼,實際過程最好以引數的形式對外提供

基於AudioRecord

這種方式需要我們自己線上程中,迴圈讀取音訊資料:

public class AudioRecordRecorder implements IAudioRecorder {

    private static final String TAG = "AudioRecordRecorder";

    private int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;

    /**
     * 音訊取樣率
     */
    public static int SAMPLE_RATE = 44100;

    /**
     * 單聲道
     */
    public final static int CHANNEL = AudioFormat.CHANNEL_IN_STEREO;

    /**
     * 16位元
     */
    public final static int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;

    /**
     * 音訊錄製例項
     */
    protected AudioRecord audioRecord;

    /**
     * 錄製執行緒
     */
    private Thread recordThread;

    /**
     * 輸出的檔案路徑
     */
    private String pcmPath;

    /**
     * 緩衝區大小
     */
    private int bufferSize = 0;

    /**
     * 是否正在錄製
     */
    private boolean isRecording = false;

    /**
     * 回撥原始的PCM資料
     */
    private OnAudioRecordListener mOnAudioRecordListener;

    public AudioRecordRecorder(String filePath) {
        this.pcmPath = filePath;
    }

    @Override
    public void initRecorder() {
        if (null != audioRecord) {
            audioRecord.release();
        }
        try {
            bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL, AUDIO_FORMAT);
            audioRecord = new AudioRecord(AUDIO_SOURCE, SAMPLE_RATE, CHANNEL, AUDIO_FORMAT, bufferSize);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public int recordStart() {
        if (isRecording) {
            return RECORD_STATE.STATE_RECORDING;
        } else if (audioRecord != null && audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
            try {
                audioRecord.startRecording();
                isRecording = true;
                recordThread = new Thread(new AudioRecordRunnable());
                recordThread.start();
                return RECORD_STATE.STATE_SUCCESS;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return RECORD_STATE.STATE_ERROR;
    }

    /**
     * 停止音訊錄製
     */
    @Override
    public void recordStop() {
        try {
            if (audioRecord != null) {
                isRecording = false;
                try {
                    if (recordThread != null) {
                        recordThread.join();
                        recordThread = null;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //釋放資源
                recordRelease();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void recordRelease() {
        if (audioRecord != null) {
            if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
                audioRecord.stop();
            }
            audioRecord.release();
            audioRecord = null;
        }
    }

    private class AudioRecordRunnable implements Runnable {

        private FileOutputStream outputStream = null;

        @Override
        public void run() {
            try {
                if (!TextUtils.isEmpty(pcmPath)) {
                    outputStream = new FileOutputStream(pcmPath);
                }
                byte[] audioBuffer = new byte[bufferSize];
                while (isRecording && audioRecord != null) {
                    int audioSampleSize = audioRecord.read(audioBuffer, 0, bufferSize);
                    if (audioSampleSize > 0) {
                        if (outputStream != null) {
                            outputStream.write(audioBuffer);
                        }
                        if (mOnAudioRecordListener != null) {
                            mOnAudioRecordListener.onAudioBuffer(audioBuffer, audioBuffer.length);
                        }
                    }
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                close(outputStream);
                outputStream = null;
            }
        }
    }

    private void close(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void setOnAudioRecordListener(OnAudioRecordListener onAudioRecordListener) {
        this.mOnAudioRecordListener = onAudioRecordListener;
    }

    public interface OnAudioRecordListener {

        void onAudioBuffer(byte[] buffer, int length);

    }

}

工程實踐

新建工程ffmpeg-audio-encode,在應用的點選事件中處理如下:

/**
     * 點選開始錄製按鈕
     * @param view
     */
    public void onRecordStart(View view) {
        File targetDir = getExternalFilesDir(null);
        if (audioRecorder == null) {
            audioRecorder = new MediaRecordRecorder(targetDir.getAbsolutePath()+ File.separator+"output.aac");
            //audioRecorder = new AudioRecordRecorder(targetDir.getAbsolutePath()+ File.separator+"output.pcm");
        }
        audioRecorder.initRecorder();
        audioRecorder.recordStart();
        //更新按鈕狀態
        mBtnStart.setEnabled(false);
        mBtnStop.setEnabled(true);
    }

    /**
     * 點選停止按鈕
     * @param view
     */
    public void onRecordStop(View view) {
        if (audioRecorder != null) {
            audioRecorder.recordStop();
            audioRecorder = null;
        }
        //更新按鈕狀態
        mBtnStart.setEnabled(true);
        mBtnStop.setEnabled(false);
    }

基於MediaRecorder方式我們可以直接得到output.aac檔案,這個直接使用系統的音訊播放器即可播放

基於AudioRecord方式我們可以直接得到output.ocm檔案,需要匯出到電腦上,使用ffplay播放器進行播放

ffplay -ar 44100 -channels 2 -f s16le -i output.pcm

專案地址:ffmpeg-audio-encode
https://github.com/byhook/ffmpeg4android