Android實(shí)現(xiàn)錄音(MediaRecorder)

Android提供了兩個API用于錄音的實(shí)現(xiàn):MediaRecorder 和 AudioRecord侮邀,各有優(yōu)劣函筋。

1、MediaRecorder

已經(jīng)集成了錄音再愈、編碼榜苫、壓縮等,支持少量的錄音音頻格式翎冲,大概有.aac(API = 16) .amr .3gp
優(yōu)點(diǎn):大部分已經(jīng)集成垂睬,直接調(diào)用相關(guān)接口即可,代碼量小
缺點(diǎn):無法實(shí)時(shí)處理音頻;輸出的音頻格式不是很多驹饺,例如沒有輸出mp3格式文件

2钳枕、AudioRecord

主要是實(shí)現(xiàn)邊錄邊播(AudioRecord+AudioTrack)以及對音頻的實(shí)時(shí)處理(如會說話的湯姆貓、語音)
優(yōu)點(diǎn):語音的實(shí)時(shí)處理赏壹,可以用代碼實(shí)現(xiàn)各種音頻的封裝
缺點(diǎn):輸出是PCM語音數(shù)據(jù)鱼炒,如果保存成音頻文件,是不能夠被播放器播放的蝌借,所以必須先寫代碼實(shí)現(xiàn)數(shù)據(jù)編碼以及壓縮

先說 MediaRecorder : MediaRecorder因?yàn)榇蟛糠止δ芤呀?jīng)集成昔瞧,所以使用起來相對比較簡單。
下面是個小demo:

① 界面
界面比較簡單菩佑,由于MediaRecorder 并不能實(shí)現(xiàn)暫停自晰、繼續(xù)錄音的功能

錄音界面.png
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_start"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="開始" />

    <Button
        android:id="@+id/btn_stop"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:enabled="false"
        android:text="停止" />

    <TextView
        android:id="@+id/text_time"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="00:00:00"
        android:padding="5dp"
        android:layout_marginTop="10dp"/>

</LinearLayout>

② 相關(guān)錄音功能

開始錄音
    public void startRecord() {
        // 開始錄音
        /* ①Initial:實(shí)例化MediaRecorder對象 */
        if (mMediaRecorder == null)
            mMediaRecorder = new MediaRecorder();
        try {
            /* ②setAudioSource/setVedioSource */
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 設(shè)置麥克風(fēng)
            /*
             * ②設(shè)置輸出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式
             * ,H263視頻/ARM音頻編碼)稍坯、MPEG-4缀磕、RAW_AMR(只支持音頻且音頻編碼要求為AMR_NB)
             */
            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
            /* ②設(shè)置音頻文件的編碼:AAC/AMR_NB/AMR_MB/Default 聲音的(波形)的采樣 */
            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
            fileName = DateFormat.format("yyyyMMdd_HHmmss", Calendar.getInstance(Locale.CHINA)) + ".m4a";
            if (!FileUtils.isFolderExist(FileUtils.getFolderName(audioSaveDir))) {
                FileUtils.makeFolders(audioSaveDir);
            }
            filePath = audioSaveDir + fileName;
            /* ③準(zhǔn)備 */
            mMediaRecorder.setOutputFile(filePath);
            mMediaRecorder.prepare();
            /* ④開始 */
            mMediaRecorder.start();
        } catch (IllegalStateException e) {
            LogUtil.i("call startAmr(File mRecAudioFile) failed!" + e.getMessage());
        } catch (IOException e) {
            LogUtil.i("call startAmr(File mRecAudioFile) failed!" + e.getMessage());
        }
    }

音頻編碼可以根據(jù)自己實(shí)際需要自己設(shè)定,文件名防止重復(fù)劣光,使用了日期_時(shí)分秒的結(jié)構(gòu),audioSaveDir 是文件存儲目錄糟把,可自行設(shè)定绢涡。

停止錄音
    public void stopRecord() {
        try {
            mMediaRecorder.stop();
            mMediaRecorder.release();
            mMediaRecorder = null;
            filePath = "";
        } catch (RuntimeException e) {
            LogUtil.e(e.toString());
            mMediaRecorder.reset();
            mMediaRecorder.release();
            mMediaRecorder = null;

            File file = new File(filePath);
            if (file.exists())
                file.delete();

            filePath = "";
        }
    }
時(shí)長記錄
    // 記錄錄音時(shí)長
    private void countTime() {
        while (isRecording) {
                LogUtil.d("正在錄音");
                timeCount++;
                Message msg = Message.obtain();
                msg.what = TIME_COUNT;
                msg.obj = timeCount;
                myHandler.sendMessage(msg);
            try {
                timeThread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        LogUtil.d("結(jié)束錄音");
        timeCount = 0;
        Message msg = Message.obtain();
        msg.what = TIME_COUNT;
        msg.obj = timeCount;
        myHandler.sendMessage(msg);
    }
將錄音時(shí)長格式化
    // 格式化 錄音時(shí)長為 時(shí):分:秒
    public static String FormatMiss(int miss) {
        String hh = miss / 3600 > 9 ? miss / 3600 + "" : "0" + miss / 3600;
        String mm = (miss % 3600) / 60 > 9 ? (miss % 3600) / 60 + "" : "0" + (miss % 3600) / 60;
        String ss = (miss % 3600) % 60 > 9 ? (miss % 3600) % 60 + "" : "0" + (miss % 3600) % 60;
        return hh + ":" + mm + ":" + ss;
    }
Activity全部代碼
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.text.format.DateFormat;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.zzx.hellokotlin.R;
import com.zzx.hellokotlin.utils.FileUtils;
import com.zzx.hellokotlin.utils.LogUtil;

import java.io.File;
import java.io.IOException;
import java.util.Calendar;
import java.util.Locale;

public class Record2Activity extends AppCompatActivity {


    // 錄音界面相關(guān)
    Button btnStart;
    Button btnStop;
    TextView textTime;

    // 錄音功能相關(guān)
    MediaRecorder mMediaRecorder; // MediaRecorder 實(shí)例
    boolean isRecording; // 錄音狀態(tài)
    String fileName; // 錄音文件的名稱
    String filePath; // 錄音文件存儲路徑
    Thread timeThread; // 記錄錄音時(shí)長的線程
    int timeCount; // 錄音時(shí)長 計(jì)數(shù)
    final int TIME_COUNT = 0x101;
    // 錄音文件存放目錄
    final String audioSaveDir = Environment.getExternalStorageDirectory().getAbsolutePath()+"/audiodemo/";

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

        btnStart = (Button) findViewById(R.id.btn_start);
        btnStop = (Button) findViewById(R.id.btn_stop);
        textTime = (TextView) findViewById(R.id.text_time);

        btnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 開始錄音
                btnStart.setEnabled(false);
                btnStop.setEnabled(true);

                startRecord();
                isRecording = true;
                // 初始化錄音時(shí)長記錄
                timeThread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        countTime();
                    }
                });
                timeThread.start();
            }
        });

        btnStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 停止錄音
                btnStart.setEnabled(true);
                btnStop.setEnabled(false);

                stopRecord();
                isRecording = false;
            }
        });

    }

    // 記錄錄音時(shí)長
    private void countTime() {
        while (isRecording) {
                LogUtil.d("正在錄音");
                timeCount++;
                Message msg = Message.obtain();
                msg.what = TIME_COUNT;
                msg.obj = timeCount;
                myHandler.sendMessage(msg);
            try {
                timeThread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        LogUtil.d("結(jié)束錄音");
        timeCount = 0;
        Message msg = Message.obtain();
        msg.what = TIME_COUNT;
        msg.obj = timeCount;
        myHandler.sendMessage(msg);
    }


    /**
     * 開始錄音 使用amr格式
     * 錄音文件
     * @return
     */
    public void startRecord() {
        // 開始錄音
        /* ①Initial:實(shí)例化MediaRecorder對象 */
        if (mMediaRecorder == null)
            mMediaRecorder = new MediaRecorder();
        try {
            /* ②setAudioSource/setVedioSource */
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 設(shè)置麥克風(fēng)
            /*
             * ②設(shè)置輸出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式
             * ,H263視頻/ARM音頻編碼)遣疯、MPEG-4雄可、RAW_AMR(只支持音頻且音頻編碼要求為AMR_NB)
             */
            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
            /* ②設(shè)置音頻文件的編碼:AAC/AMR_NB/AMR_MB/Default 聲音的(波形)的采樣 */
            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
            fileName = DateFormat.format("yyyyMMdd_HHmmss", Calendar.getInstance(Locale.CHINA)) + ".m4a";
            if (!FileUtils.isFolderExist(FileUtils.getFolderName(audioSaveDir))) {
                FileUtils.makeFolders(audioSaveDir);
            }
            filePath = audioSaveDir + fileName;
            /* ③準(zhǔn)備 */
            mMediaRecorder.setOutputFile(filePath);
            mMediaRecorder.prepare();
            /* ④開始 */
            mMediaRecorder.start();
        } catch (IllegalStateException e) {
            LogUtil.i("call startAmr(File mRecAudioFile) failed!" + e.getMessage());
        } catch (IOException e) {
            LogUtil.i("call startAmr(File mRecAudioFile) failed!" + e.getMessage());
        }
    }

    /**
     * 停止錄音
     */
    public void stopRecord() {
        //有一些網(wǎng)友反應(yīng)在5.0以上在調(diào)用stop的時(shí)候會報(bào)錯,翻閱了一下谷歌文檔發(fā)現(xiàn)上面確實(shí)寫的有可能會報(bào)錯的情況缠犀,捕獲異常清理一下就行了数苫,感謝大家反饋!
        try {
            mMediaRecorder.stop();
            mMediaRecorder.release();
            mMediaRecorder = null;
            filePath = "";

        } catch (RuntimeException e) {
            LogUtil.e(e.toString());
            mMediaRecorder.reset();
            mMediaRecorder.release();
            mMediaRecorder = null;

            File file = new File(filePath);
            if (file.exists())
                file.delete();

            filePath = "";
        }
    }

    // 格式化 錄音時(shí)長為 時(shí):分:秒
    public static String FormatMiss(int miss) {
        String hh = miss / 3600 > 9 ? miss / 3600 + "" : "0" + miss / 3600;
        String mm = (miss % 3600) / 60 > 9 ? (miss % 3600) / 60 + "" : "0" + (miss % 3600) / 60;
        String ss = (miss % 3600) % 60 > 9 ? (miss % 3600) % 60 + "" : "0" + (miss % 3600) % 60;
        return hh + ":" + mm + ":" + ss;
    }


    Handler myHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case TIME_COUNT:
                    int count = (int) msg.obj;
                    LogUtil.d("count == " + count);
                    textTime.setText(FormatMiss(count));

                    break;
            }
        }
    };
}

總結(jié):MediaRecorder 實(shí)現(xiàn)錄音還是比較簡單的辨液,只是不能暫停虐急。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市滔迈,隨后出現(xiàn)的幾起案子止吁,更是在濱河造成了極大的恐慌,老刑警劉巖燎悍,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敬惦,死亡現(xiàn)場離奇詭異,居然都是意外死亡谈山,警方通過查閱死者的電腦和手機(jī)俄删,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人畴椰,你說我怎么就攤上這事臊诊。” “怎么了迅矛?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵妨猩,是天一觀的道長。 經(jīng)常有香客問我秽褒,道長壶硅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任销斟,我火速辦了婚禮庐椒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蚂踊。我一直安慰自己约谈,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布犁钟。 她就那樣靜靜地躺著棱诱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涝动。 梳的紋絲不亂的頭發(fā)上迈勋,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機(jī)與錄音醋粟,去河邊找鬼靡菇。 笑死,一個胖子當(dāng)著我的面吹牛米愿,可吹牛的內(nèi)容都是我干的厦凤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼育苟,長吁一口氣:“原來是場噩夢啊……” “哼较鼓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起违柏,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤笨腥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后勇垛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脖母,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年闲孤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谆级。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烤礁。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖肥照,靈堂內(nèi)的尸體忽然破棺而出脚仔,到底是詐尸還是另有隱情,我是刑警寧澤舆绎,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布鲤脏,位于F島的核電站,受9級特大地震影響吕朵,放射性物質(zhì)發(fā)生泄漏猎醇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一努溃、第九天 我趴在偏房一處隱蔽的房頂上張望硫嘶。 院中可真熱鬧,春花似錦梧税、人聲如沸沦疾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哮塞。三九已至,卻和暖如春凳谦,著一層夾襖步出監(jiān)牢的瞬間彻桃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工晾蜘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人眠屎。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓剔交,卻偏偏與公主長得像,于是被迫代替她去往敵國和親改衩。 傳聞我的和親對象是個殘疾皇子岖常,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容