Android 实现录音功能

思路:

通过媒体录制器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视频.mp4MPEG4格式
THREE_GPP视频.3gp3GP格式

音频录制示例,上代码

一.权限添加

<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://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

<!-- 这里的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>

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android实现录音功能,可以使用 `MediaRecorder` 类或 `AudioRecord` 类。这里给出 `AudioRecord` 的实现示例。 首先,在 AndroidManifest.xml 文件中添加录音权限: ```xml <uses-permission android:name="android.permission.RECORD_AUDIO" /> ``` 然后,创建一个 `AudioRecord` 对象并开始录音: ```java import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; import android.os.Handler; import android.os.Looper; import android.util.Log; public class AudioRecorder { private static final String TAG = AudioRecorder.class.getSimpleName(); private static final int SAMPLE_RATE = 16000; private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO; private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; private static final int BUFFER_SIZE = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT); private AudioRecord audioRecord; private byte[] buffer; private boolean isRecording; private Handler handler; private Callback callback; public AudioRecorder() { audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, BUFFER_SIZE); buffer = new byte[BUFFER_SIZE]; handler = new Handler(Looper.getMainLooper()); } public void setCallback(Callback callback) { this.callback = callback; } public void start() { if (isRecording) { return; } audioRecord.startRecording(); isRecording = true; new Thread(() -> { while (isRecording) { int bytes = audioRecord.read(buffer, 0, buffer.length); if (bytes > 0 && callback != null) { handler.post(() -> callback.onAudioDataReceived(buffer, bytes)); } } audioRecord.stop(); audioRecord.release(); audioRecord = null; }).start(); } public void stop() { isRecording = false; } public interface Callback { void onAudioDataReceived(byte[] data, int size); } } ``` 在此示例中,我们首先创建一个 `AudioRecord` 对象,并使用 `MediaRecorder.AudioSource.MIC` 作为录音源。然后,我们在一个新线程中读取录音数据,并调用回调函数将数据传递给主线程。最后,我们在 `stop` 方法中停止录音。 使用示例: ```java AudioRecorder recorder = new AudioRecorder(); recorder.setCallback((data, size) -> { // 处理录音数据 }); recorder.start(); // 录音中... recorder.stop(); ``` 请注意,此示例中的 `Callback` 接口仅用于演示目的,您应该将其替换为您自己的接口,并在其中处理录音数据。另外,如果您需要将录音保存到文件中,可以使用 `AudioRecord` 的 `read` 方法读取录音数据,并将其写入文件中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值