android 語音即時通訊之錄音、播放實現

在android中,實現錄音與語音播放的功能算是比較簡單的,但是作為參考,還是很有必要將語音相關的知識做一個簡要的記錄。

首先,在android中,支持錄音支持兩種方式。主要包括:字節(jié)流模式和文件流模式。用文件流模式進行錄音操作比較簡單,而且相對來說,因為其封裝性比較好,錄制下的文件也會比較小。但是相對于文件流模式,就沒有字節(jié)流模式那么靈活,但是想要用好字節(jié)流模式還是需要下一點功夫的。

下面開始介紹文件流模式的語音操作:

文件流模式

我們來看錄音部分的實現,首先我們實現開始錄音的部分:
在正式編碼之前,還是需要對其進行一個簡要的說明。一般來說,錄音功能的實現是在jin層,而在這一層中,是用單線程實現的。如果我們在編碼的對錄音api進行多線程操作,會導致程序直接閃退,并且我們是無法在java層對其異常進行捕獲的。所以,我們必須使用單線程以保證錄音的正常運行。
一般來說,開始錄音的步驟也就三個,代碼如下:

releaseRecord();//釋放可能沒釋放的錄音相關資源
if (!doStartRecord()) {//真正的開始錄音的函數,開始錄音成功返回true,否則返回false
   recordFail(); //開始失敗,向用戶提示開始錄音失敗
}

接下來我們來看看上述三個方法的實現:
實現是釋放相關資源的方法releaseRecord:

 if (mMediaRecorder != null) {
            mMediaRecorder.release();
            mMediaRecorder = null;
}

其中的mMediaRecorder 的MediaRecorder的全局變量。
接下來是真正實現開始錄音的實現邏輯doStartRecord()

 private boolean doStartRecord() {
        try {
            mMediaRecorder = new MediaRecorder();

            mAudioFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/voice/"
                    + System.currentTimeMillis() + ".m4a");
            mAudioFile.getParentFile().mkdirs();
            mAudioFile.createNewFile();

            //設置從麥克風采集聲音
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);

            //保存文件為mp4的格式
            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);

            //設置所有android系統(tǒng)都支持的采樣頻率
            mMediaRecorder.setAudioSamplingRate(44100);

            //設置acc的編碼方式
            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);

            //設置比較好的音質
            mMediaRecorder.setAudioEncodingBitRate(96000);

            mMediaRecorder.setOutputFile(mAudioFile.getAbsolutePath());

            mMediaRecorder.prepare();
            mMediaRecorder.start();

            mStartRecordTime = System.currentTimeMillis();

        } catch (IOException | RuntimeException e) {
            e.printStackTrace();
            return false;
        }finally {
            if(mAudioRecord != null){
                mAudioRecord.release();
            }
        }

        return true;
    }

這一部分代碼比較多,但是關鍵部分我都給出了注釋,相信理解起來也不算難吧。這一部分我們實現的主要是在sdcrad根目錄下新建一個voice的目錄,然后在新建一個以==.m4a==為后綴名的文件。在配置mAudioRecord的相關參數后,將收集到的錄音存放到之前的文件中。如果一切都順利的話,就返回true ,表示開始錄音成功。

最后就是提示用戶錄音實現的邏輯recordFail

      mAudioFile = null;
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(VioceActivity.this, "錄音失敗", Toast.LENGTH_SHORT).show();
            }
        }, 100);

這里的邏輯比較簡單,但是值得注意的是,因為我們開始錄音方法是在一個非主線程的線程中執(zhí)行的,所以我們需要借助hander來實現界面提示的效果。這里的mHander是一個局部變量,其初始化放在OnCreate()方法中。
mHandler = new Handler(Looper.getMainLooper());
綜上,開始錄音的所有邏輯已經全部實現。接下來實現的是結束錄音的實現邏輯:
主題的邏輯如下:

 if (!doStopRecord()) {//實現 停止錄音的真正邏輯,成功返回true,否則返回false
                    recordFail();//提示用戶錄音失敗
                }

這里的doStopRecord實現邏輯如下:

  mMediaRecorder.stop();
            mEndRecordTime = System.currentTimeMillis();

            final int seond = (int) ((mEndRecordTime - mStartRecordTime) / 1000);

            if (seond < 3) {
                recordFail();
                return false;
            } else {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mText.setText("錄音" + seond + "成功!");
                    }
                });
            }
        } catch (RuntimeException e) {
            e.printStackTrace();
            return  false;
        }
        return true;

其實我們實現停止錄音的邏輯也很簡單,首先調用mMediaRecorder.stop();停止錄音,然后對錄音時間是否大于3s進行判斷,若大于3s,則表示錄音有效,提示用戶,錄音成功。
綜上,我們文件流的錄音的所有代碼已經實現完畢。接下來我們實現對其進行播放。如果需要參考全部的代碼,請戳這里

 private void doPlay(File mAudioFile) {
        //配置播放器 MediaPlayer
        mMediaPlayer = new MediaPlayer();

        try{

            //設置聲音文件
            mMediaPlayer.setDataSource(mAudioFile.getAbsolutePath());

            //設置監(jiān)聽回調
            mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mp) {
                    stopPlay();
                }
            });

            //設置出錯的監(jiān)聽器
            mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
                @Override
                public boolean onError(MediaPlayer mp, int what, int extra) {
                    playFail();
                    //提示用戶
                    stopPlay();
                    //釋放播放器
                    return true;
                }
            });

            //配置音量,是否循環(huán)
            mMediaPlayer.setVolume(1,1);
            mMediaPlayer.setLooping(false);

            mMediaPlayer.prepare();
            mMediaPlayer.start();
        }catch (Exception  e){
            e.printStackTrace();
            playFail();
            stopPlay();
        }
    }

整體來說,基于文件的錄音是比較容易實現的。下面介紹如何通過字節(jié)流模式實現錄音。

字節(jié)流模式錄音

開始錄音:主要邏輯startRecord2()的實現

private boolean startRecord2() {
        try {
            mAudioFile2  = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/voice/"
                    + System.currentTimeMillis() + ".pcm");
            mAudioFile2.getParentFile().mkdirs();
            mAudioFile2.createNewFile();

            mFileOutputStream = new FileOutputStream(mAudioFile2);
            //配置AudioRecord

            //從麥克風采集數據
            int audioSource = MediaRecorder.AudioSource.MIC;

            //采集頻率
            int sampleRate = 44100;

            //單聲道輸入
            int channelConfig = AudioFormat.CHANNEL_IN_MONO;

            //設置pcm(脈沖編碼調制 Pulse Code Modulation)編碼格式
            int audioFormat = AudioFormat.ENCODING_PCM_16BIT;

            //計算AudioRecord 內存buffer最小的大小
            int minBufferSize = AudioRecord.getMinBufferSize(sampleRate,channelConfig,audioFormat);
            //創(chuàng)建AudioRecord對象
            mAudioRecord = new AudioRecord(audioSource,sampleRate,channelConfig,audioFormat,Math.max(BUFFER_SIZE,minBufferSize));

            mAudioRecord.startRecording();
            mStartRecordTime = System.currentTimeMillis();

            //循環(huán)讀取數據,寫到輸出流中
            while(mIsRecord){
                int read = mAudioRecord.read(mBuffer,0,BUFFER_SIZE);
                if(read >0 ){
                    //讀取文件寫到文件中
                    mFileOutputStream.write(mBuffer,0,read);
                }else{
                    return false;
                }
            }

        } catch (IOException | RuntimeException e) {
            e.printStackTrace();
            return false;
        }

        //停止錄音
        return true;
    }

停止錄音的doStopRecord()實現:

 private boolean doStopRecord() {
        try {

            mMediaRecorder.stop();
            mEndRecordTime = System.currentTimeMillis();

            final int seond = (int) ((mEndRecordTime - mStartRecordTime) / 1000);

            if (seond < 3) {
                recordFail();
                return false;
            } else {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mText.setText("錄音" + seond + "成功!");
                    }
                });
            }
        } catch (RuntimeException e) {
            e.printStackTrace();
            return  false;
        }
        return true;
    }

對其中相關參數的說名:

 private boolean mIsRecord = false;

    private final int BUFFER_SIZE = 2048;//緩存區(qū)的大小

    private byte[] mBuffer;

    private FileOutputStream mFileOutputStream;

    private AudioRecord mAudioRecord;

    private File mAudioFile2;

接下來,實現的是對其字節(jié)流模式錄制的文件進行播放:doPlay2()主題類的實現:

private void doPlay2(File mAudioFile) {

        //聲音類型,揚聲器播放
        int steamType = AudioManager.STREAM_MUSIC;

        //采樣頻率
        int sampleRate = 44100;

        //MONO 表示單聲道 錄音輸入單聲道 播放也使用單聲道
        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);

        AudioTrack audioTrack = new AudioTrack(steamType,sampleRate,channelConfig,audioFormat,
                Math.max(minBufferSize,BUFFER_SIZE),mode);

        //從文件流中讀取數據
        FileInputStream inputStream = null;

        try{
            inputStream = new FileInputStream(mAudioFile2);
            int read;
            //循環(huán)讀取數據,寫到播放器去播放
            audioTrack.play();
            while((read = inputStream.read(mBuffer)) > 0){
                int ret = audioTrack.write(mBuffer,0,read);
            switch (ret){
                case AudioTrack.ERROR:
                case AudioTrack.ERROR_BAD_VALUE:
                case AudioTrack.ERROR_INVALID_OPERATION:
                case AudioTrack.ERROR_DEAD_OBJECT:
                        playFail();
                        return ;
                default:
                    break;
            }
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            mIsPlaying = false;

           if(inputStream != null)
               closeQuietly(inputStream);
            resetQuietly(audioTrack);
        }

    }

千言萬語肯定不如直接代碼來的直接了當,所以的代碼實現點這里
實現的效果如下:

22-59-38.jpg
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,690評論 25 708
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,726評論 18 399
  • 學習了麥肯錫工作方法,如同找到職場能力提升的捷徑。 三十秒法則 這是個屢試不爽的法則,尤其在電梯里遇到大老板,使用...
    Be_myself閱讀 200評論 0 4
  • 今天很累 再堅持一下 過完今天就好了 明天未知 也不一定輕松 過完明天就好了 后天在三步開外 面無表情 生活總是很...
    觥嫦閱讀 254評論 0 2