Android 錄音功能直接拿去用

前言

最近項目中須要用到錄音的功能,借鑑了外國一位哥們的項目github.com/dkim0419/So…,搞定需求以後,花了些時間封裝成一個錄音的工具包,分享給你們,須要源碼的 點擊這裏android

先貼個效果圖給你們看一下,看看這個錄音包的功能git

1、實現錄音的 Service

這個類能夠說是這個包的核心了,若是理解了這個 Service,錄音這一塊基本就沒什麼問題了。github

錄音主要是利用 MediaRecoder 這個類,進行聲音的記錄,接下來咱們一塊兒來看看具體的實現。面試

public class RecordingService extends Service {
 
    private String mFileName;
    private String mFilePath;
 
    private MediaRecorder mRecorder;
 
    private long mStartingTimeMillis;
    private long mElapsedMillis;
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        startRecording();
        return START_STICKY;
    }
 
    @Override
    public void onDestroy() {
        if (mRecorder != null) {
            stopRecording();
        }
        super.onDestroy();
    }
 
    // 開始錄音
    public void startRecording() {
        setFileNameAndPath();
 
        mRecorder = new MediaRecorder();
        mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); //錄音文件保存的格式,這裏保存爲 mp4
        mRecorder.setOutputFile(mFilePath); // 設置錄音文件的保存路徑
        mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        mRecorder.setAudioChannels(1);
        // 設置錄音文件的清晰度
        mRecorder.setAudioSamplingRate(44100);
        mRecorder.setAudioEncodingBitRate(192000);
 
        try {
            mRecorder.prepare();
            mRecorder.start();
            mStartingTimeMillis = System.currentTimeMillis();
        } catch (IOException e) {
            Log.e(LOG_TAG, "prepare() failed");
        }
    }
 
    // 設置錄音文件的名字和保存路徑
    public void setFileNameAndPath() {
        File f;
 
        do {
            count++;
            mFileName = getString(R.string.default_file_name)
                    + "_" + (System.currentTimeMillis()) + ".mp4";
            mFilePath = Environment.getExternalStorageDirectory().getAbsolutePath();
            mFilePath += "/SoundRecorder/" + mFileName;
            f = new File(mFilePath);
        } while (f.exists() && !f.isDirectory());
    }
 
    // 中止錄音
    public void stopRecording() {
        mRecorder.stop();
        mElapsedMillis = (System.currentTimeMillis() - mStartingTimeMillis);
        mRecorder.release();
 
        getSharedPreferences("sp_name_audio", MODE_PRIVATE)
                .edit()
                .putString("audio_path", mFilePath)
                .putLong("elpased", mElapsedMillis)
                .apply();
        if (mIncrementTimerTask != null) {
            mIncrementTimerTask.cancel();
            mIncrementTimerTask = null;
        }
 
        mRecorder = null;
    }
 
}
複製代碼

能夠看到在 onStartCommand() 裏面有一個 startRecording() 方法,在外部啓動這個RecordingService 的時候,便會調用這個 startRecording() 方法開始錄音。小程序

在 startRecording() 方法中先調用了 setFileNameAndPath 方法,初始化了錄音文件的名字和保存的路徑,爲了讓每一個錄音文件都有惟一的名字,我調用System.currentMillis() 拼接到錄音文件的名字裏面。性能優化

public void setFileNameAndPath() {
       File f;

       do {
           count++;
           mFileName = getString(R.string.default_file_name)
                   + "_" + (System.currentTimeMillis()) + ".mp4";
           mFilePath = Environment.getExternalStorageDirectory().getAbsolutePath();
           mFilePath += "/SoundRecorder/" + mFileName;
           f = new File(mFilePath);
       } while (f.exists() && !f.isDirectory());
   }
複製代碼

設置好了文件的名字和保存路徑以後,對 mRecorder 進行一系列參數的設置,這個mRecorder 是 MediaRecorder 的一個實例,專門用於錄音的存儲。bash

mRecorder = new MediaRecorder();
       mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
       mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); //錄音文件保存的格式,這裏保存爲 mp4
       mRecorder.setOutputFile(mFilePath); // 設置錄音文件的保存路徑
       mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
       mRecorder.setAudioChannels(1);
       // 設置錄音文件的清晰度
       mRecorder.setAudioSamplingRate(44100);
       mRecorder.setAudioEncodingBitRate(192000);

       try {
           mRecorder.prepare();
           mRecorder.start();
           mStartingTimeMillis = System.currentTimeMillis();
       } catch (IOException e) {
           Log.e(LOG_TAG, "prepare() failed");
       }
複製代碼

設置好參數以後,啓動 mRecorder 開始錄音,能夠看到啓動 mRecorder 開始錄音後,我還將當前的時間賦值給 mStartingTimeMills,這裏主要是爲了記錄錄音的時長,等到錄音結束後再獲取一次當前的時間,而後將兩個時間進行相減,就能獲得錄音的具體時長了。架構

等到錄音結束,中止服務後,便會回調 RecordingService 的 onDestroy() 方法,這時候便會調用 stopRecording() 方法,關閉 mRecorder,並用 SharedPreferences 保存錄音文件的信息,最後將 mRecorder 置空,防止內存泄露app

public void stopRecording() {
        mRecorder.stop();
        mElapsedMillis = (System.currentTimeMillis() - mStartingTimeMillis);
        mRecorder.release();
 
        getSharedPreferences("sp_name_audio", MODE_PRIVATE)
                .edit()
                .putString("audio_name", mFileName)
                .putString("audio_path", mFilePath)
                .putLong("elpased", mElapsedMillis)
                .apply();
        if (mIncrementTimerTask != null) {
            mIncrementTimerTask.cancel();
            mIncrementTimerTask = null;
        }
 
        mRecorder = null;
    }
複製代碼

2、顯示錄音界面的 RecordAudioDialogFragment

用戶進行的時候,總不能讓 App 跳轉到另一個界面吧,這樣用戶體驗並非很好,比較好的方法是顯示一個對話框,讓用戶進行操做,既然要用對話框,必然離不開 DialogFragment。框架

public class RecordAudioDialogFragment extends DialogFragment {
 
    private boolean mStartRecording = true;
 
    long timeWhenPaused = 0;
 
    private FloatingActionButton mFabRecord;
    private Chronometer mChronometerTime;
 
    public static RecordAudioDialogFragment newInstance(int maxTime) {
        RecordAudioDialogFragment dialogFragment = new RecordAudioDialogFragment();
        Bundle bundle = new Bundle();
        bundle.putInt("maxTime", maxTime);
        dialogFragment.setArguments(bundle);
        return dialogFragment;
    }
 
    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Dialog dialog = super.onCreateDialog(savedInstanceState);
        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        View view = getActivity().getLayoutInflater().inflate(R.layout.fragment_record_audio, null);
 
        mFabRecord.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(getActivity()
                            , new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}, 1);
                }else {
                    onRecord(mStartRecording);
                    mStartRecording = !mStartRecording;
                }
            }
        });
 
        builder.setView(view);
        return builder.create();
    }
 
    private void onRecord(boolean start) {
        Intent intent = new Intent(getActivity(), RecordingService.class);
        if (start) {
            File folder = new File(Environment.getExternalStorageDirectory() + "/SoundRecorder");
            if (!folder.exists()) {
                folder.mkdir();
            }
 
            mChronometerTime.setBase(SystemClock.elapsedRealtime());
            mChronometerTime.start();
            getActivity().startService(intent);
 
        } else {
            mChronometerTime.stop();
            timeWhenPaused = 0;
            getActivity().stopService(intent);
        }
    }
}
複製代碼

能夠看到在 RecordAudioDialogFragment 有一個 newInstance(int maxTime) 的靜態方法供外部調用,若是想設置錄音的最大時長,直接傳參數進去就好了。

好的,敲黑板,重點來了,其實這個對話框的重點部分就是在 onCreateDialog()中,咱們先加載了咱們自定義的對話框的佈局,當點擊錄音的按鈕的時候,先進行相關權限的申請,這裏有個巨坑,錄音權限 android.permission.RECORD_AUDIO 在不久前仍是普通權限的,不知道何時忽然變成了危險權限,須要咱們進行申請,Google 真是會玩。

public Dialog onCreateDialog(Bundle savedInstanceState) {
        Dialog dialog = super.onCreateDialog(savedInstanceState);
        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        View view = getActivity().getLayoutInflater().inflate(R.layout.fragment_record_audio, null);
 
        mFabRecord.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(getActivity()
                            , new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}, 1);
                }else {
                    onRecord(mStartRecording);
                    mStartRecording = !mStartRecording;
                }
            }
        });
 
        builder.setView(view);
        return builder.create();
    }
複製代碼

申請好權限以後便會調用 onRecord() 這個方法,而後將 boolean mStartRecording 進行反轉,這樣就不用寫難看的 if else 了,直接改變 mStartRecording 的值,而後在onRecord() 裏面進行處理

接下來看下 onRecord 幹了什麼

private void onRecord(boolean start) {
        Intent intent = new Intent(getActivity(), RecordingService.class);
        if (mStartRecording) {
            File folder = new File(Environment.getExternalStorageDirectory() + "/SoundRecorder");
            if (!folder.exists()) {
                folder.mkdir();
            }
 
            mChronometerTime.setBase(SystemClock.elapsedRealtime());
            mChronometerTime.start();
            getActivity().startService(intent);
 
        } else {
            mChronometerTime.stop();
            timeWhenPaused = 0;
            getActivity().stopService(intent);
        }
    }
複製代碼

好吧,其實並無幹了什麼大事,只是建立了保存錄音文件的文件夾,而後根據mStartRecording 的值進行 RecordingService 的啓動和關閉罷了。在啓動時還順便開始了 mChronometer 的計時顯示,這是一個 Android 原生的顯示計時的一個控件。

3、播放錄音的 PlaybackDialogFragment

其實,若是隻是錄音這一塊的話,寫個 MediaPlayer 就能夠了,然而還要寫播放的時間進度,以及顯示一個稍微好看點的進度條,我能怎樣,我也很煩啊。

外部調用這個對話框的時候,只須要傳入一個包含錄音文件信息的 RecordingItem,由於包含的信息比較多,因此最好將 RecordingItem 進行序列化。

public static PlaybackDialogFragment newInstance(RecordingItem item) {
        PlaybackDialogFragment fragment = new PlaybackDialogFragment();
        Bundle bundle = new Bundle();
        bundle.putParcelable(ARG_ITEM, item);
        fragment.setArguments(b);
        return fragment;
    }
複製代碼

好,重點又來了,來看看 onCreateDialog() 方法,在加載了佈局以後,給 mSeekBar 設置監聽,mSeekBar 是一個顯示進度條的控件,當開始播放錄音時候,將錄音文件的時長,設置進 mSeekBar 裏面,播放錄音的同時,運行 mSeekBar,經過監聽 mSeekBar 的進度,刷新顯示的播放進度。

public Dialog onCreateDialog(Bundle savedInstanceState) {
 
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        View view = getActivity().getLayoutInflater().inflate(R.layout.fragment_media_playback, null);
 
        mTvFileLength.setText(String.valueOf(mFileLength));
        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if(mMediaPlayer != null && fromUser) {
                    mMediaPlayer.seekTo(progress);
                    mHandler.removeCallbacks(mRunnable);
 
                    long minutes = TimeUnit.MILLISECONDS.toMinutes(mMediaPlayer.getCurrentPosition());
                    long seconds = TimeUnit.MILLISECONDS.toSeconds(mMediaPlayer.getCurrentPosition())
                            - TimeUnit.MINUTES.toSeconds(minutes);
                    mCurrentProgressTextView.setText(String.format("%02d:%02d", minutes,seconds));
 
                    updateSeekBar();
 
                } else if (mMediaPlayer == null && fromUser) {
                    prepareMediaPlayerFromPoint(progress);
                    updateSeekBar();
                }
            }
 
        });
 
        mPlayButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onPlay(isPlaying);
                isPlaying = !isPlaying;
            }
        });
 
        mTvFileLength.setText(String.format("%02d:%02d", minutes,seconds));
        builder.setView(view);
        return builder.create();
    }
複製代碼

當點擊播放錄音的按鈕以後,會調用 onPlay() 方法,而後根據 isPlaying(標識當前是否播放錄音)的值,來調用不一樣的方法

private void onPlay(boolean isPlaying){
        if (!isPlaying) {
            if(mMediaPlayer == null) {
                startPlaying(); //start from beginning
            } 
        } else {
            pausePlaying();
        }
    }
複製代碼

咱們最關心的,莫過於 startPlaying() 這個方法,這個方法即是來開啓播放錄音的,咱們首先將外部傳入的有關的錄音信息,設置給 MediaPlayer,而後開始調用mMediaPlayer.start() 進行錄音的播放,而後調用 updateSeekbar() 實時更新進度條的內容。當 MediaPlayer 的內容播放完成後,調用 stopPlaying() 方法,關閉mMediaPlayer。

private void startPlaying() {
        mMediaPlayer = new MediaPlayer();
        mMediaPlayer.setDataSource(item.getFilePath());
        mMediaPlayer.prepare();
        mSeekBar.setMax(mMediaPlayer.getDuration());
 
        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                    mMediaPlayer.start();
                }
            });
 
        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                stopPlaying();
            }
        });
        updateSeekBar();
    }
複製代碼

以上即是本文的所有內容,喜歡的能夠關注

想學習更多Android知識,或者獲取相關資料請加入Android開發交流羣:1018342383。 有面試資源系統整理分享,Java語言進階和Kotlin語言與Android相關技術內核,APP開發框架知識, 360°Android App全方位性能優化。Android前沿技術,高級UI、Gradle、RxJava、小程序、Hybrid、 移動架構師專題項目實戰環節、React Native、等技術教程!架構師課程、NDK模塊開發、 Flutter等全方面的 Android高級實踐技術講解。還有在線答疑

相關文章
相關標籤/搜索