Android音訊處理知識(一)MediaRecorder錄製音訊

筆墨Android發表於2018-06-01

在Android中處理音訊方面的知識一直是一塊很重要的知識,正好最近公司做一個關於打卡的內容,所以正好總結一下相應的知識,其實這塊的知識挺雜挺亂的,所以這個不打算一次講解完,分開給大家講解,如果有什麼不對的,希望大家一起討論下!怎麼突然感覺自己正經起來挺可笑的呢?哈哈!!!

Android音訊處理知識(一)MediaRecorder錄製音訊

先嘮叨一下關於平時開發時候的一個細節,我也是最近才覺得這個東西比較有用的!什麼呢?大家平時接到任務的時候怎麼著手開發的呢?說簡單點,當你開始敲程式碼的時候是怎麼開始的!最近我看見我同事有一個很好的習慣,他每次開始敲程式碼的時候,先拿張紙寫下來,然後簡單分析一下,其實我平時也這樣。但是重點不在這裡,每次我分析完了就直接寫程式碼了?然後又什麼BUG改什麼BUG。他不是,他寫每一個頁面都先搭一個架子,把具體的每一塊程式碼都分工明確,其實就是把方法都抽解出來,他還和我說,這樣做的好處是如果你某一塊程式碼不會寫的話,找別人寫就OK了,不會對自己的內容產生影響!其實我感覺這種做法是很好的,所以和大家分享一下!!!如果你有什麼更好的方案,不妨分享一下!!!

關於音訊的內容包括一下的知識點:

處理方式 檔案方式 位元組流方式
錄製音訊 MediaRecorder AudioRecord
播放音訊 MediaPlayer AudioTrack

因為這裡面每個內容都涉及到很多內容,所以這裡準備一塊一塊去講!感覺這樣總結的話,還能講的更加透徹一點

本文知識點


  • MediaRecorder錄製音訊

1. MediaRecorder錄製音訊

MediaRecorder錄製音訊是不保證執行緒安全的。其次,MediaRecorder採集音訊是以檔案的形式儲存的,所以你可以不用去考慮相應的位元組寫入問題,這點比較簡單。其他的知識我會在後面使用到的時候進行講解!

1.1 MediaRecoeder錄製音訊注意的一些問題

當我們錄製音訊的時候要考慮以下幾個問題?

  • 錄製音訊的時候在那個執行緒執行?如果不是在主執行緒執行的話,怎麼保證狀態的及時更新?
  • MediaRecorder錄製開始時有哪些設定?
  • 出現的異常怎麼解決?
  • 錄製音訊的時候使用者按下HOME鍵怎麼處理?
  • 使用者退出頁面需要哪些操作?
  • 錄製音訊檔案的名稱需要注意什麼?

以上這些問題都是我們需要考慮的!其實簡單的實現錄音的話相對簡單一些,但是如果所有的問題都加到一塊的話,就比較複雜了!!!我們還是一點一點開始說吧

1.2 開始錄製音訊的準備

為了能給大家講解明白!所以我覺得我還是由必要嘮叨幾句,強烈建議大家把架子先搭建好!養成了這樣的習慣可以讓你在一個月甚至更長的時間回頭看程式碼的時候,最起碼不會說!這他媽是誰寫的程式碼?然後默默的看了署名居然是自己的!!!我現在在單位些程式碼,基本上都在最上面把裡面要實現的內容大體寫寫,然後註釋寫的稍微多點,最起碼在你以後離開公司的時候別人不會說你!!!相信我,有的時候公司之前寫的程式碼,真的是看不懂!!!

許可權千萬別忘了

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
複製程式碼

首先先現象錄製音訊我們都需要什麼?許可權不能少吧!6.0許可權的適配不能少吧?首先狀態你不能少吧,反正很多東西要考慮!先說一下我的處理思路吧!我建立了一個執行緒去處理相應的音訊錄製,我通過一個按鈕,在按下的時候開始錄製音訊,鬆開的時候停止錄製音訊,成功的話就儲存相應的音訊,如果時間小於3秒的話,就放棄儲存檔案(其實這裡是刪除了相應的檔案,因為錄製之前已經把檔案路徑傳進去了,你錄製多久都會有相應檔案)!對了這裡還有一個問題需要注意,就是錄製檔案的名稱問題?我們專案在以前儲存錄制檔案的名稱是帶有"-"的,像這樣"2018-09-23-15:23:23"這種形式的,在大多數手機上是沒有問題的,但是在OPPO手機上怎麼也播放不出來,後來才知道有"-"就不行,後來乾脆就直接用時間戳了,怎麼也不會重複的!說了這麼多,估計你也煩了,好吧!我先把基礎的架子貼出來!!!

public class FileActivity extends AppCompatActivity {


    @BindView(R.id.tv_show)
    TextView mTvShow;
    @BindView(R.id.btn_start)
    TextView mBtnStart;
    @BindView(R.id.btn_play)
    TextView mBtnPlay;

    private ExecutorService mExecutorService;
    private Handler mMainHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_file);
        ButterKnife.bind(this);

        mMainHandler = new Handler(Looper.getMainLooper());
        mExecutorService = Executors.newSingleThreadExecutor();

        //對按鈕進行監聽
        mBtnStart.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        //開始錄音
                        startRecord();
                        break;
                    case MotionEvent.ACTION_UP:
                    case MotionEvent.ACTION_CANCEL:
                        //停止錄音
                        stopRecord();
                        break;
                }
                return true;
            }
        });
    }

    /**
     * 開始錄音
     */
    private void startRecord() {
        //更改UI的狀態
        mBtnStart.setText("鬆手就可以停止錄音");

        //開始錄音
        mExecutorService.submit(new Runnable() {
            @Override
            public void run() {
                //釋放之前存在的資源,因為這裡是按下的時候就開始錄音,所以按下的時候一定要先釋放相應的資源
                releaseResources();
                if (!doStart()) {//不成功彈Toast提示使用者
                    ToastFail();//提示使用者
                }
            }
        });
    }

    /**
     * 提示使用者失敗資訊
     */
    private void ToastFail() {

    }

    /**
     * 開始錄音是否成功,
     * 所以所有的邏輯就直接在這裡去寫了
     */
    private boolean doStart() {
        return true;
    }


    /**
     * 停止錄音
     */
    private void stopRecord() {
        mBtnStart.setText("按下開始錄音");
        mExecutorService.submit(new Runnable() {
            @Override
            public void run() {

                if(!doStop()){
                    ToastFail();
                }
                //這裡停止後應該,釋放相應的資源
                releaseResources();
            }
        });
    }

    /**
     * 停止錄音
     */
    private boolean doStop() {
        //這裡說處理停止播放的邏輯
        return false;
    }

    /**
     * 釋放資源
     */
    private void releaseResources() {

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //在主執行緒關閉的時候一定要停止執行緒,避免記憶體洩露
        mExecutorService.shutdownNow();
    }
}
複製程式碼

註釋還是比較全的,相信大家一看就能看懂,看不懂的可以騷擾我!!!

1.3 MediaRecorder錄製音訊的設定

這裡主要是設定MediaRecorder的一些配置,主要包括一下幾個內容。

  • 建立MediaRecorder物件
  • 建立錄音檔案儲存的位置
  • 配置MediaRecorder
  • 開始錄音
  • 儲存開始時間,因為這裡要做時間判斷,所以要記錄開始時間

基本上就以上這些內容,這裡要是展開講太多,其實是我也不會!真的,沒個流媒體工程師你能說你會?不可能的!

    /**
     * 開始錄音是否成功,
     * 所以所有的邏輯就直接在這裡去寫了
     */
    private boolean doStart() {
        /*
         * 1.建立MediaRecorder物件
         * 2.建立相應的儲存檔案
         * 3.配置相應的MediaRecorder
         * 4.開始錄音
         */
        try {
            //建立mediaRecorder物件
            MediaRecorder recorder = new MediaRecorder();
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                //建立儲存的檔案
                String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Video/" + System.currentTimeMillis() + ".m4a";
                mAudioFile = new File(path);
                mAudioFile.getParentFile().mkdirs();
                mAudioFile.createNewFile();

                /*
                 * 重點來了,配置MediaRecorder
                 */
                //配置採集方式,這裡用的是麥克風的採集方式
                recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
                //配置輸出方式,這裡用的是MP4,
                recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
                //配置取樣頻率,頻率越高月接近原始聲音,Android所有裝置都支援的取樣頻率為44100
                recorder.setAudioSamplingRate(44100);
                //配置檔案的編碼格式,AAC是比較通用的編碼格式
                recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
                //配置位元速率,這裡一般通用的是96000
                recorder.setAudioEncodingBitRate(96000);
                //配置錄音檔案的位置
                recorder.setOutputFile(mAudioFile.getAbsolutePath());

                //開始錄製音訊
                recorder.prepare();//準備
                recorder.start();//開始錄音

                /*因為這裡要做相應的時間判斷,所以要做時間的記錄*/
                mStartRecordTime = System.currentTimeMillis();

            } else {
                //因為這裡SD卡沒有掛在,所以就直接返回false,如果你真的想在專案中使用的話,需要判斷記憶體大小什麼的,這裡為了簡便就沒有寫,
                //但是現在基本上都沒事,因為手機內容都很大,但是如果做專案的話這些都要做的!
                return false;
            }
        } catch (IOException|RuntimeException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
複製程式碼

我個人感覺已經寫的很清楚了,你就這麼整就一定能開始錄製音訊,別點贊。要臉!!!然後你在失敗的ToastFail方法中隨便提示點什麼就可以了,但是有一點需要注意,你要注意,這個ToastFail方法是線上程中執行的,那麼問題來了?怎麼在主執行緒提示呢?眼尖的童鞋應該注意到我在最開始的時候定義了一個主執行緒的Handler,這個時候它就排上用場了!整體程式碼如下:

    /**
     * 提示使用者失敗資訊
     */
    private void ToastFail() {
        mAudioFile = null;
        mMainHandler.post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(FileActivity.this, "錄製音訊失敗", Toast.LENGTH_SHORT).show();
            }
        });
    }
複製程式碼

1.4 MediaRecorder停止錄音的方法

其實這裡的邏輯還是很簡單的,基本上就是呼叫一個stop的方法,判斷一下時間,和上面一樣,需要注意執行緒的問題,做Android的應該都知道,在非UI執行緒裡面修改UI會報異常的!!!程式碼就像這個樣子!

    /**
     * 停止錄音
     */
    private boolean doStop() {
        try {
            //這裡說處理停止播放的邏輯
            mMediaRecorder.stop();

            //因為這裡要判斷相應的時間,如果大於三秒就直接儲存,否則刪除檔案
            mEndRecordTime = System.currentTimeMillis();
            final int time = (int) ((mEndRecordTime - mStartRecordTime) / 1000);
            if (time > 3) {
                mMainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        String des = "錄音成功" + time + "秒";
                        mTvShow.setText(des);
                    }
                });
            } else {
                if (mAudioFile.exists()) {
                    mAudioFile.delete();
                }
                mMainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mTvShow.setText("錄音小於3秒沒有儲存檔案");
                    }
                });
            }
        } catch (RuntimeException e) {
            e.printStackTrace();
            Log.e(TAG, "doStop: " + e);
            return false;
        }
        return true;
    }
複製程式碼

上面就是停止錄音的方法了,基本的實現邏輯就是大於3秒鐘儲存檔案,否則就刪除檔案,其實MediaRecorder儲存檔案,不管你錄製多少秒都會儲存的!所以為了節約空間,所以這裡做了刪除的操作!就是把之前錄製的檔案進行刪除!

1.5 釋放相應的資源

其實釋放資源就比較簡單了,直接釋放了滯空就可以了

    /**
     * 釋放資源
     */
    private void releaseResources() {
        if (mMediaRecorder != null) {
            mMediaRecorder.release();
            mMediaRecorder = null;
        }
    }
複製程式碼

關於音訊的錄製就這麼多的內容了,對了!提醒一下,測試的時候最好找個真機去測試,虛擬機器會有各種各樣的毛病!真心的,這一點千萬要記得。我沒有做許可權的適配,因為不是重點!!!好了今天就到這裡吧。詹姆斯輸球了,很是不開心,激動的我一上午沒怎麼寫程式碼,能贏的比賽就這麼葬送了。。。可憐了詹姆斯啊!!!不說了,心塞。。。

忘了說了,程式碼地址

相關文章