思路:
通过媒体录制器MediaRecorder实现:MediaRecorder是Android自带的音频和视频录制工具,它通过操纵摄像头和麦克风完成媒体录制,既可录制视频,又可单独录制音频。
MediaRecorder常用方法(录音与录像通用):
- reset:重置录制资源。
- prepare:准备录制。
- start:开始录制。
- stop:结束录制。
- release:释放录制资源。
- setOnErrorListener:设置错误监听器。可监听服务器异常和未知错误的事件。需要实现接口MediaRecorder.OnErrorListener的onError方法。
- setOnInfoListener:设置信息监听器。可监听录制结束事件,包括达到录制时长或达到录制大小。需要实现接口MediaRecorder.OnInfoListener的onInfo方法。
- setMaxDuration:设置可录制的最大时长,单位毫秒。
- setMaxFileSize:设置可录制的最大文件大小,单位字节。
- setOutputFile:设置输出文件的路径。
setOutputFormat:设置媒体输出格式 | |||
OutputFormat类的输出格式 | 格式分类 | 扩展名 | 格式说明 |
AMR_NB | 音频 | .amr | 窄带格式 |
AMR_WB | 音频 | .amr | 宽带格式 |
AAC_ADTS | 音频 | .aac | 高级的音频传输流格式 |
MPEG_4 | 视频 | .mp4 | MPEG4格式 |
THREE_GPP | 视频 | .3gp | 3GP格式 |
音频录制示例,上代码
一.权限添加
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
权限区分版本33及以上和以下
动态权限请求:
使用权限请求库,在app的build.gradle添加
// 权限请求
implementation 'com.guolindev.permissionx:permissionx:1.7.1'
使用音频抖动动效文章来源:https://www.toymoban.com/news/detail-657179.html
// https://github.com/xfans/VoiceWaveView
implementation 'com.github.xfans:VoiceWaveView:1.0.2'
/**
* 请求视频、音频、图片权限
*/
private void requestPermission() {
ArrayList<String> requestList = new ArrayList<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requestList.add(Manifest.permission.READ_MEDIA_IMAGES);
requestList.add(Manifest.permission.RECORD_AUDIO);
requestList.add(Manifest.permission.READ_MEDIA_VIDEO);
} else {
requestList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
requestList.add(Manifest.permission.READ_EXTERNAL_STORAGE);
requestList.add(Manifest.permission.RECORD_AUDIO);
}
PermissionX.init(this)
.permissions(requestList)
.onExplainRequestReason((scope, deniedList) -> {
scope.showRequestReasonDialog(deniedList, UiUtil.getString(R.string.toast_permission_request), UiUtil.getString(R.string.toast_permission_allow), UiUtil.getString(R.string.toast_permission_deny));
})
.request((allGranted, grantedList, deniedList) -> {
if (allGranted) {
showVoiceAddDialog();
LogUtil.i(TAG, "所有申请的权限都已通过");
} else {
LogUtil.i(TAG, "您拒绝了如下权限:" + deniedList);
}
});
}
/**
* 判断是否有存储、音频权限
* @return
*/
public boolean hasStoragePermissions() {
boolean isStorage, isAudio, isVideo, isImages;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
isImages = ContextCompat.checkSelfPermission(MyApplication.getInstance(), Manifest.permission.READ_MEDIA_IMAGES)
== PackageManager.PERMISSION_GRANTED;
isAudio = ContextCompat.checkSelfPermission(MyApplication.getInstance(), Manifest.permission.RECORD_AUDIO)
== PackageManager.PERMISSION_GRANTED;
isVideo = ContextCompat.checkSelfPermission(MyApplication.getInstance(), Manifest.permission.READ_MEDIA_VIDEO)
== PackageManager.PERMISSION_GRANTED;
return isImages && isAudio && isVideo;
} else {
isStorage = ContextCompat.checkSelfPermission(MyApplication.getInstance(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED;
isAudio = ContextCompat.checkSelfPermission(MyApplication.getInstance(), Manifest.permission.RECORD_AUDIO)
== PackageManager.PERMISSION_GRANTED;
return isStorage || isAudio;
}
}
二.录制代码
package com.calendar.master.gp.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.format.DateFormat;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import com.calendar.master.gp.R;
import com.calendar.master.gp.bean.NoteVoice;
import com.calendar.master.gp.databinding.DialogAddVoiceBinding;
import com.calendar.master.gp.databinding.DialogEmojiBinding;
import com.calendar.master.gp.fragment.EmojiFragment;
import com.calendar.master.gp.listener.IVoiceSaveListener;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import com.oooppqqzzz.base.utils.LogUtil;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import me.xfans.lib.voicewaveview.VoiceWaveView;
/**
* 描述:
* 作者: shawn
* 时间: 2023/4/2716:44
*/
public class VoiceAddDialogFragment extends DialogFragment {
private static final String TAG = "VoiceAddDialogFragment";
private MediaRecorder mMediaRecorder;
private String voicePath;
private String outputFileName;
private File fileVoice;
private IVoiceSaveListener iVoiceSaveListener;
private DialogAddVoiceBinding mBinding;
private int recordingTime = 0; // 记录录音时长(秒)
private Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
// 更新UI显示录音时长
mBinding.tvVoiceTime.setText(getRecordTime(recordingTime));
}
};
;
@Override
public void onStart() {
super.onStart();
setCustomStyle();
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
if (getDialog() == null) {
return super.onCreateView(inflater, container, savedInstanceState);
}
// 设置宽度为屏宽、靠近屏幕底部。
final Window window = getDialog().getWindow();
window.setBackgroundDrawableResource(R.color.transparent);
window.getDecorView().setPadding(0, 0, 0, 0);
WindowManager.LayoutParams wlp = window.getAttributes();
wlp.gravity = Gravity.BOTTOM;
wlp.width = WindowManager.LayoutParams.MATCH_PARENT;
wlp.height = WindowManager.LayoutParams.WRAP_CONTENT;
window.setAttributes(wlp);
return super.onCreateView(inflater, container, savedInstanceState);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// 加载布局文件
mBinding = DialogAddVoiceBinding.inflate(getLayoutInflater());
// 音频抖动控件
VoiceWaveView voiceWaveView = mBinding.vwvVoice;
voiceWaveView.setDuration(100);
voiceWaveView.addHeader(12).addHeader(14).addHeader(26);
voiceWaveView.addBody(27).addBody(17).addBody(38)
.addBody(88)
.addBody(38)
.addBody(24)
.addBody(8)
.addBody(18)
.addBody(48)
.addBody(30)
.addBody(60)
.addBody(38);
voiceWaveView.addFooter(24).addFooter(14).addFooter(8);
mBinding.ivClose.setOnClickListener(v -> {
this.dismiss();
});
mBinding.ivVoiceEdit.setOnClickListener(new View.OnClickListener() {
boolean isInput = true;
@Override
public void onClick(View v) {
if (isInput) {
isInput = false;
voiceWaveView.start();
mBinding.ivVoiceEdit.setBackgroundResource(R.mipmap.icon_voice_edit_stop);
getPath();
startRecord();
} else {
isInput = true;
voiceWaveView.stop();
cancelRecord();
// 移除定时任务
handler.removeCallbacks(timerRunnable);
voiceStop(true);
}
}
});
mBinding.ivVoiceCancel.setOnClickListener(v -> {
voiceStop(false);
fileVoice.delete();
recordingTime = 0;
mBinding.tvVoiceTime.setText("00:00");
mBinding.ivVoiceEdit.setBackgroundResource(R.mipmap.icon_voice_edit_start);
});
mBinding.ivVoiceSave.setOnClickListener(v -> {
NoteVoice noteVoice = new NoteVoice();
noteVoice.setUrl(voicePath);
noteVoice.setTime(getRecordTime(recordingTime));
noteVoice.setFileName(outputFileName);
if (iVoiceSaveListener != null) {
iVoiceSaveListener.save(noteVoice);
}
dismiss();
});
return new AlertDialog.Builder(getActivity()).setView(mBinding.getRoot())
.create();
}
private void showAd(int selectIndex) {
}
private void voiceStop(boolean isStop) {
if (isStop) {
mBinding.ivVoiceCancel.setVisibility(View.VISIBLE);
mBinding.ivVoiceSave.setVisibility(View.VISIBLE);
mBinding.ivVoiceEdit.setVisibility(View.GONE);
} else {
mBinding.ivVoiceCancel.setVisibility(View.GONE);
mBinding.ivVoiceSave.setVisibility(View.GONE);
mBinding.ivVoiceEdit.setVisibility(View.VISIBLE);
}
}
private void setCustomStyle() {
// 设置主题,这里只能通过xml方式设置主题,不能通过Java代码处理,因为这是getWindow还是null,
// 而且window的几乎所有属性,都可以通过xml设置
setStyle(STYLE_NORMAL, R.style.AddDialogTheme);
}
/**
* 录制前创建一个空文件并获取路径
*/
private void getPath() {
File appDir = new File(Environment.getExternalStorageDirectory() + File.separator + Environment.DIRECTORY_MUSIC, "NoteToVoice");
if (!appDir.exists()) {
appDir.mkdirs();
}
outputFileName = System.currentTimeMillis() + ".amr";
fileVoice = new File(appDir, outputFileName);
if (!fileVoice.exists()) {
try {
fileVoice.createNewFile();
voicePath = fileVoice.getPath();
LogUtil.i("shawn", "音频保存路径:" + voicePath);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 开始录音
*/
private void startRecord() {
// 充值录音时长
recordingTime = 0;
//开始录音操作
mMediaRecorder = new MediaRecorder(); // 创建一个媒体录制器
mMediaRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() {
@Override
public void onError(MediaRecorder mr, int what, int extra) {
if (mr != null) {
mr.reset(); // 重置媒体录制器
}
}
}); // 设置媒体录制器的错误监听器
mMediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
@Override
public 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) {
cancelRecord();
}
}
}); // 设置媒体录制器的信息监听器
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 设置音频源为麦克风
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB); // 设置媒体的输出格式
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); // 设置媒体的音频编码器
// mMediaRecorder.setAudioSamplingRate(8); // 设置媒体的音频采样率。可选
// mMediaRecorder.setAudioChannels(2); // 设置媒体的音频声道数。可选
// mMediaRecorder.setAudioEncodingBitRate(1024); // 设置音频每秒录制的字节数。可选
mMediaRecorder.setMaxDuration(10 * 1000); // 设置媒体的最大录制时长
// mMediaRecorder.setMaxFileSize(1024*1024*10); // 设置媒体的最大文件大小
// setMaxFileSize与setMaxDuration设置其一即可
mMediaRecorder.setOutputFile(voicePath); // 设置媒体文件的保存路径
try {
mMediaRecorder.prepare(); // 媒体录制器准备就绪
mMediaRecorder.start(); // 媒体录制器开始录制
startRecordingTimer();
} catch (Exception e) {
LogUtil.i("shawn", "录音出错 = " + e.getMessage());
e.printStackTrace();
}
}
// 取消录制操作
private void cancelRecord() {
if (mMediaRecorder != null) {
mMediaRecorder.setOnErrorListener(null); // 错误监听器置空
mMediaRecorder.setPreviewDisplay(null); // 预览界面置空
try {
LogUtil.i("shawn", "结束录音");
mMediaRecorder.stop(); // 媒体录制器停止录制
} catch (Exception e) {
LogUtil.i("shawn", "结束录音出错 = " + e.getMessage());
e.printStackTrace();
}
mMediaRecorder.release(); // 媒体录制器释放资源
mMediaRecorder = null;
}
}
@Override
public void onDestroy() {
super.onDestroy();
// 移除定时任务
handler.removeCallbacks(timerRunnable);
}
public void setVoiceSaveListener(IVoiceSaveListener iVoiceSaveListener) {
this.iVoiceSaveListener = iVoiceSaveListener;
}
private void startRecordingTimer() {
handler.postDelayed(timerRunnable, 1000); // 每隔一秒执行一次
}
private Runnable timerRunnable = new Runnable() {
@Override
public void run() {
recordingTime++;
handler.sendEmptyMessage(0); // 通知UI更新
startRecordingTimer(); // 继续下一次定时
}
};
private String getRecordTime(int recordingTime) {
StringBuilder stringBuilder = new StringBuilder();
if (recordingTime < 10) {
stringBuilder.append("00:0").append(recordingTime);
} else if (recordingTime < 60) {
stringBuilder.append("00:").append(recordingTime);
} else {
int minutes = recordingTime / 60;
int seconds = recordingTime % 60;
if (minutes < 10) {
stringBuilder.append("0").append(minutes).append(":");
} else {
stringBuilder.append(minutes).append(":");
}
if (seconds < 10) {
stringBuilder.append("0").append(seconds);
} else {
stringBuilder.append(seconds);
}
}
return stringBuilder.toString();
}
}
设置dialog样式,在style.xml文章来源地址https://www.toymoban.com/news/detail-657179.html
<!-- 这里的parent必须是Theme.AppCompat.Dialog -->
<style name="AddDialogTheme" parent="Theme.AppCompat.Dialog">
<!-- 这两个属性对于一个常规的Dialog,一般必须设置的-->
<!-- 这两个属性按照下面的值设置之后,确保了弹窗的实际显示效果,跟你在layout文件中的定义效果是一样的 -->
<item name="android:windowIsFloating">false</item>
<!-- <item name="android:windowBackground">@drawable/radius_add_dialog_bg</item>-->
</style>
到了这里,关于Android 实现录音功能的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!