布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="40dp"android:paddingLeft="5dp"><TextViewandroid:layout_width="wrap_content"android:layout_height="match_parent"android:gravity="center"android:text="音频编码:"android:textColor="@color/black"android:textSize="17sp" /><Spinnerandroid:id="@+id/sp_encoder"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="left|center"android:spinnerMode="dialog" /></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="40dp"android:paddingLeft="5dp"><TextViewandroid:layout_width="wrap_content"android:layout_height="match_parent"android:gravity="center"android:text="输出格式:"android:textColor="@color/black"android:textSize="17sp" /><Spinnerandroid:id="@+id/sp_format"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="left|center"android:spinnerMode="dialog" /></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="40dp"android:paddingLeft="5dp"><TextViewandroid:layout_width="wrap_content"android:layout_height="match_parent"android:gravity="center"android:text="录制时长:"android:textColor="@color/black"android:textSize="17sp" /><Spinnerandroid:id="@+id/sp_duration"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="left|center"android:spinnerMode="dialog" /></LinearLayout><Buttonandroid:id="@+id/btn_record"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="开始录音"android:textColor="@color/black"android:textSize="17sp" /><LinearLayoutandroid:id="@+id/ll_progress"android:layout_width="match_parent"android:layout_height="30dp"android:paddingLeft="5dp"android:paddingRight="5dp"android:orientation="horizontal"android:visibility="gone"><ProgressBarandroid:id="@+id/pb_record"style="?android:attr/progressBarStyleHorizontal"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="4" /><TextViewandroid:id="@+id/tv_progress"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="right|center"android:textColor="@color/black"android:textSize="15sp" /></LinearLayout><ImageViewandroid:id="@+id/iv_audio"android:layout_width="match_parent"android:layout_height="50dp"android:scaleType="fitCenter"android:src="@drawable/play_audio"android:visibility="gone" /></LinearLayout>
MediaUtil
package com.example.myapplication.util;import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;import java.io.File;@SuppressLint("DefaultLocale")
public class MediaUtil {private final static String TAG = "MediaUtil";// 格式化播放时长(mm:ss)public static String formatDuration(int milliseconds) {int seconds = milliseconds / 1000;int hour = seconds / 3600;int minute = seconds / 60;int second = seconds % 60;String str;if (hour > 0) {str = String.format("%02d:%02d:%02d", hour, minute, second);} else {str = String.format("%02d:%02d", minute, second);}return str;}// 获得音视频文件的缓存路径public static String getRecordFilePath(Context context, String dir_name, String extend_name) {String path = "";File recordDir = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + "/" + dir_name + "/");if (!recordDir.exists()) {recordDir.mkdirs();}try {File recordFile = File.createTempFile(DateUtil.getNowDateTime(), extend_name, recordDir);path = recordFile.getAbsolutePath();Log.d(TAG, "dir_name=" + dir_name + ", extend_name=" + extend_name + ", path=" + path);} catch (Exception e) {e.printStackTrace();}return path;}// 获取视频文件中的某帧图片public static Bitmap getOneFrame(Context ctx, Uri uri) {MediaMetadataRetriever retriever = new MediaMetadataRetriever();retriever.setDataSource(ctx, uri);// 获得视频的播放时长,大于1秒的取第1秒处的帧图,不足1秒的取第0秒处的帧图String duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);Log.d(TAG, "duration="+duration);int pos = (Integer.parseInt(duration)/1000)>1 ? 1 : 0;// 获取指定时间的帧图,注意getFrameAtTime方法的时间单位是微秒return retriever.getFrameAtTime(pos * 1000 * 1000);}
}
item_select.xml
<TextView xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="50dp"android:singleLine="true"android:gravity="center"android:textSize="17sp"android:textColor="#0000ff" />
主代码:
package com.example.myapplication;import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.example.myapplication.util.MediaUtil;
import androidx.appcompat.app.AppCompatActivity;
import java.util.Timer;
import java.util.TimerTask;public class MainActivity extends AppCompatActivity implements View.OnClickListener, MediaRecorder.OnInfoListener {private static final String TAG = "MediaRecorderActivity";private Button btn_record;private LinearLayout ll_progress;private ProgressBar pb_record; // 声明一个进度条对象private TextView tv_progress;private ImageView iv_audio; // 该图标充当播放按钮private MediaRecorder mMediaRecorder = new MediaRecorder(); // 媒体录制器private boolean isRecording = false; // 是否正在录制private int mAudioEncoder; // 音频编码private int mOutputFormat; // 输出格式private int mDuration; // 录制时长private String mRecordFilePath; // 录制文件的保存路径private Timer mTimer = new Timer(); // 计时器private int mTimeCount; // 时间计数private MediaPlayer mMediaPlayer = new MediaPlayer(); // 媒体播放器@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn_record = findViewById(R.id.btn_record);ll_progress = findViewById(R.id.ll_progress);pb_record = findViewById(R.id.pb_record);tv_progress = findViewById(R.id.tv_progress);iv_audio = findViewById(R.id.iv_audio);btn_record.setOnClickListener(this);iv_audio.setOnClickListener(this);initEncoderSpinner(); // 初始化音频编码的下拉框initFormatSpinner(); // 初始化输出格式的下拉框initDurationSpinner(); // 初始化录制时长的下拉框}// 初始化音频编码的下拉框private void initEncoderSpinner() {ArrayAdapter<String> encoderAdapter = new ArrayAdapter<String>(this,R.layout.item_select, encoderDescArray);Spinner sp_encoder = findViewById(R.id.sp_encoder);sp_encoder.setPrompt("请选择音频编码"); // 设置下拉框的标题sp_encoder.setAdapter(encoderAdapter); // 设置下拉框的数组适配器// 给下拉框设置选择监听器,一旦用户选中某一项,就触发监听器的onItemSelected方法sp_encoder.setOnItemSelectedListener(new EncoderSelectedListener());sp_encoder.setSelection(0); // 设置下拉框默认显示第一项}private String[] encoderDescArray = {"默认编码","窄带编码","宽带编码","低复杂度的高级编码","高效率的高级编码","增强型低延时的高级编码"};private int[] encoderArray = {MediaRecorder.AudioEncoder.DEFAULT,MediaRecorder.AudioEncoder.AMR_NB,MediaRecorder.AudioEncoder.AMR_WB,MediaRecorder.AudioEncoder.AAC,MediaRecorder.AudioEncoder.HE_AAC,MediaRecorder.AudioEncoder.AAC_ELD};class EncoderSelectedListener implements AdapterView.OnItemSelectedListener {public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {mAudioEncoder = encoderArray[arg2];}public void onNothingSelected(AdapterView<?> arg0) {}}// 初始化输出格式的下拉框private void initFormatSpinner() {ArrayAdapter<String> formatAdapter = new ArrayAdapter<String>(this,R.layout.item_select, formatDescArray);Spinner sp_format = findViewById(R.id.sp_format);sp_format.setPrompt("请选择输出格式"); // 设置下拉框的标题sp_format.setAdapter(formatAdapter); // 设置下拉框的数组适配器sp_format.setSelection(0); // 设置下拉框默认显示第一项// 给下拉框设置选择监听器,一旦用户选中某一项,就触发监听器的onItemSelected方法sp_format.setOnItemSelectedListener(new FormatSelectedListener());}private String[] formatDescArray = {"默认格式","窄带格式","宽带格式","高级的音频传输流格式"};private int[] formatArray = {MediaRecorder.OutputFormat.DEFAULT,MediaRecorder.OutputFormat.AMR_NB,MediaRecorder.OutputFormat.AMR_WB,MediaRecorder.OutputFormat.AAC_ADTS};class FormatSelectedListener implements AdapterView.OnItemSelectedListener {public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {mOutputFormat = formatArray[arg2];}public void onNothingSelected(AdapterView<?> arg0) {}}// 初始化录制时长的下拉框private void initDurationSpinner() {ArrayAdapter<String> durationAdapter = new ArrayAdapter<String>(this,R.layout.item_select, durationDescArray);Spinner sp_duration = findViewById(R.id.sp_duration);sp_duration.setPrompt("请选择录制时长"); // 设置下拉框的标题sp_duration.setAdapter(durationAdapter); // 设置下拉框的数组适配器sp_duration.setSelection(0); // 设置下拉框默认显示第一项// 给下拉框设置选择监听器,一旦用户选中某一项,就触发监听器的onItemSelected方法sp_duration.setOnItemSelectedListener(new DurationSelectedListener());}private String[] durationDescArray = {"5秒", "10秒", "20秒", "30秒", "60秒"};private int[] durationArray = {5, 10, 20, 30, 60};class DurationSelectedListener implements AdapterView.OnItemSelectedListener {public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {mDuration = durationArray[arg2];}public void onNothingSelected(AdapterView<?> arg0) {}}@Overridepublic void onClick(View v) {if (v.getId() == R.id.btn_record) {if (!isRecording) { // 未在录音startRecord(); // 开始录音} else { // 正在录音stopRecord(); // 停止录音}} else if (v.getId() == R.id.iv_audio) {mMediaPlayer.reset(); // 重置媒体播放器mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); // 设置音频流的类型为音乐try {mMediaPlayer.setDataSource(mRecordFilePath); // 设置媒体数据的文件路径mMediaPlayer.prepare(); // 媒体播放器准备就绪mMediaPlayer.start(); // 媒体播放器开始播放} catch (Exception e) {e.printStackTrace();}}}// 开始录音private void startRecord() {Log.d(TAG, "startRecord mAudioEncoder="+mAudioEncoder+", mOutputFormat="+mOutputFormat+", mDuration="+mDuration);ll_progress.setVisibility(View.VISIBLE);isRecording = !isRecording;btn_record.setText("停止录制");pb_record.setMax(mDuration); // 设置进度条的最大值mTimeCount = 0; // 时间计数清零mTimer = new Timer(); // 创建一个计时器mTimer.schedule(new TimerTask() {@Overridepublic void run() {pb_record.setProgress(mTimeCount); // 设置进度条的当前进度tv_progress.setText(MediaUtil.formatDuration(mTimeCount*1000));mTimeCount++;}}, 0, 1000); // 计时器每隔一秒就更新进度条上的录制进度// 获取本次录制的媒体文件路径mRecordFilePath = MediaUtil.getRecordFilePath(this, "RecordAudio", ".amr");// 下面是媒体录制器的处理代码mMediaRecorder.reset(); // 重置媒体录制器mMediaRecorder.setOnInfoListener(this); // 设置媒体录制器的信息监听器mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 设置音频源为麦克风mMediaRecorder.setOutputFormat(mOutputFormat); // 设置媒体的输出格式。该方法要先于setAudioEncoder调用mMediaRecorder.setAudioEncoder(mAudioEncoder); // 设置媒体的音频编码器// mMediaRecorder.setAudioSamplingRate(8); // 设置媒体的音频采样率。可选// mMediaRecorder.setAudioChannels(2); // 设置媒体的音频声道数。可选// mMediaRecorder.setAudioEncodingBitRate(1024); // 设置音频每秒录制的字节数。可选mMediaRecorder.setMaxDuration(mDuration * 1000); // 设置媒体的最大录制时长// mMediaRecorder.setMaxFileSize(1024*1024*10); // 设置媒体的最大文件大小// setMaxFileSize与setMaxDuration设置其一即可mMediaRecorder.setOutputFile(mRecordFilePath); // 设置媒体文件的保存路径try {mMediaRecorder.prepare(); // 媒体录制器准备就绪mMediaRecorder.start(); // 媒体录制器开始录制} catch (Exception e) {e.printStackTrace();}}// 停止录音private void stopRecord() {isRecording = !isRecording;btn_record.setText("开始录制");mTimer.cancel(); // 取消定时器mMediaRecorder.stop(); // 媒体录制器停止录制}@Overridepublic void onInfo(MediaRecorder mr, int what, int extra) {// 录制达到最大时长,或者达到文件大小限制,都停止录制if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED|| what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {stopRecord(); // 停止录音iv_audio.setVisibility(View.VISIBLE);Toast.makeText(this, "已结束录制,音频文件路径为"+mRecordFilePath, Toast.LENGTH_LONG).show();}}@Overrideprotected void onStop() {super.onStop();if (!TextUtils.isEmpty(mRecordFilePath) && isRecording) {stopRecord(); // 停止录音}if (mMediaPlayer.isPlaying()) { // 如果正在播放mMediaPlayer.stop(); // 停止播放}}@Overrideprotected void onDestroy() {super.onDestroy();mMediaRecorder.release(); // 释放媒体录制器mMediaPlayer.release(); // 释放媒体播放器}
}