采用 MediaRecorder 实现录音功能

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/zeqiao/article/details/84303114

快速录音需求,还要求音频文件很小,呵呵,那就干吧:

package com.zzq.mydemo.test;

import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Environment;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;

import com.zzq.mydemo.BaseActivity;
import com.zzq.mydemo.R;

import java.io.File;
import java.io.IOException;

/**
 * 录音 Activity
 * <p>
 * <activity
 * android:name=".RecordingActivity"
 * android:theme="@style/Theme.AppCompat.Dialog" />
 */

public class RecordingActivity extends BaseActivity {

    private Dialog mDialog;
    private TextView mTvCountdown;
    private TimeCount mTimeCount;
    private MediaRecorder mRecorder;
    private String mFileName;
    private String mFilePath;
    public static final String FILE_NAME = "FILE_NAME";
    public static final String FILE_PATH = "FILE_PATH";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        showDialog();
        initMediaRecorder();
        // 多加 100毫秒,用于补偿 TimeCount的时间损耗,解决跳 1秒的 BUG【目前很好用】
        mTimeCount = new TimeCount(10100, 1000);
    }

    private void initMediaRecorder() {
        mRecorder = new MediaRecorder();
        mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mRecorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS);
        mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        mRecorder.setAudioChannels(1);
        // 设置录音文件的清晰度,这里将 1s音频压缩到 1k大小
        mRecorder.setAudioSamplingRate(8000);
        mRecorder.setAudioEncodingBitRate(8000);
    }

    @Override
    protected void onDestroy() {
        stopRecording();
        if (mTimeCount != null) {
            mTimeCount.cancel();
        }
        if (mDialog != null && mDialog.isShowing()) {
            mDialog.dismiss();
            mDialog = null;
        }
        super.onDestroy();
    }

    private void showDialog() {
        mDialog = new Dialog(this, R.style.NormalDialogStyle);
        View view = View.inflate(this, R.layout.dialog_recording, null);
        WindowManager.LayoutParams lp = mDialog.getWindow().getAttributes();
        lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
        lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
        lp.gravity = Gravity.CENTER;
        mDialog.setContentView(view, lp);
        // 设置点击对话框外部是否关闭对话框
        mDialog.setCanceledOnTouchOutside(true);
        // 点击对话框外部或者点击返回键都会触发此回调
        mDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                forResult();
            }
        });
        mTvCountdown = (TextView) view.findViewById(R.id.tv_countdown);
        Button btnRecording = (Button) view.findViewById(R.id.btn_recording);
        btnRecording.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        startRecording();
                        break;
                    case MotionEvent.ACTION_UP:
                        forResult();
                        break;
                    default:
                        break;
                }
                return true;
            }
        });
        mDialog.show();
    }

    private class TimeCount extends CountDownTimer {

        private TimeCount(long millisInFuture, long countDownInterval) {
            super(millisInFuture, countDownInterval);
        }

        @Override
        public void onTick(long millisUntilFinished) {
            mTvCountdown.setText(millisUntilFinished / 1000 + "s");
        }

        @Override
        public void onFinish() {
            forResult();
        }
    }

    private void startRecording() {
        mTimeCount.start();

        setFileNameAndPath();
        // 设置录音文件的保存路径
        mRecorder.setOutputFile(mFilePath);

        try {
            mRecorder.prepare();
            mRecorder.start();
        } catch (IOException e) {
            Log.e("RecordingActivity", "prepare() failed");
        }
    }

    /**
     * 设置录音文件的名字和保存路径
     */
    private void setFileNameAndPath() {
        mFileName = System.currentTimeMillis() + ".aac";
        mFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/SoundRecorder/" + mFileName;
        File file = new File(mFilePath);
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
    }

    private void stopRecording() {
        if (mRecorder != null) {
            try {
                mRecorder.stop();
            } catch (IllegalStateException e) {
                // 如果当前java状态和jni里面的状态不一致
                Log.e("RecordingActivity", "stop() failed");
            }
            mRecorder.release();
            mRecorder = null;
        }
    }

    private void forResult() {
        Intent intent = new Intent();
        intent.putExtra(FILE_NAME, mFileName);
        intent.putExtra(FILE_PATH, mFilePath);
        setResult(RESULT_OK, intent);
        finish();
    }
}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#ffffff"
    android:orientation="vertical"
    android:paddingLeft="30dp"
    android:paddingRight="30dp">

    <TextView
        android:id="@+id/tv_countdown"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:gravity="center"
        android:text="10s"
        android:textColor="#ff0000"
        android:textSize="30sp" />

    <Button
        android:id="@+id/btn_recording"
        android:layout_width="250dp"
        android:layout_height="50dp"
        android:layout_gravity="center"
        android:layout_marginBottom="30dp"
        android:layout_marginTop="30dp"
        android:text="按住开始录音"
        android:textColor="#ffffff"
        android:textSize="18sp" />

</LinearLayout>

其中有趣的点:
1、录音前先初始化 MediaRecorder:initMediaRecorder(),省时间,做到点击录音后就可以马上去录音;
2、通过调整 setAudioSamplingRate(int i) 和 setAudioEncodingBitRate(int i) 可以控制所录音频的质量,从而调整音频文件大小;
3、退出时要及时清理垃圾;
4、TimeCount有个跳 1秒的 BUG,这是程序运行耗时不断累积造成的,通过多加 100毫秒的方式,来补偿 TimeCount 的时间损耗,解决跳 1秒的 BUG,目前很好用(不知道有没有坑 >_<|||):

mTimeCount = new TimeCount(10100, 1000);

5、设置 setCanceledOnTouchOutside(true) 时,要监听此行为并结束录音:

        // 设置点击对话框外部是否关闭对话框
        mDialog.setCanceledOnTouchOutside(true);
        // 点击对话框外部或者点击返回键都会触发此回调
        mDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                forResult();
            }
        });

6、用了 “按住录音,放开停止” 的方式:

        Button btnRecording = (Button) view.findViewById(R.id.btn_recording);
        btnRecording.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        startRecording();
                        break;
                    case MotionEvent.ACTION_UP:
                        forResult();
                        break;
                    default:
                        break;
                }
                return true;
            }
        });

下面是调用录音页面及播放所录音频的代码:

public class MainActivity extends BaseActivity {

    private MediaPlayer mMediaPlayer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    
    public void onJump(View view) {
        Intent intent = new Intent(this, RecordingActivity.class);
        startActivityForResult(intent, 1);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == Activity.RESULT_OK) {
            String fileName = data.getStringExtra(RecordingActivity.FILE_NAME);
            String filePath = data.getStringExtra(RecordingActivity.FILE_PATH);
            if (!filePath.isEmpty()) {
                if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
                    stopPlaying();
                    return;
                }
                playAudio(filePath);
            }
        }
    }

    private void playAudio(String filePath) {
        if (mMediaPlayer == null) {
            mMediaPlayer = new MediaPlayer();
            mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mp) {
                    stopPlaying();
                }
            });
        }
        try {
            mMediaPlayer.setDataSource(filePath);
            mMediaPlayer.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }
        mMediaPlayer.start();
    }

    private void stopPlaying() {
        if (mMediaPlayer != null) {
            mMediaPlayer.stop();
            mMediaPlayer.release();
            mMediaPlayer = null;
        }
    }

    @Override
    protected void onDestroy() {
        stopPlaying();
        super.onDestroy();
    }
}

参考文章:
1、https://www.jianshu.com/p/6bbb51ac4938
2、https://www.jianshu.com/p/6d91a8d7b974
3、https://blog.csdn.net/hecheng2009/article/details/74807032

猜你喜欢

转载自blog.csdn.net/zeqiao/article/details/84303114