Android13音频录制适配

Android13音频录制适配

前言:

之前写过一篇音频录制的文章,当时是在Android10以下的手机可以成功录制和播放,但是Android10及以上手机提示创建文件失败,最近做过Android13的适配,索性一起把之前的录音也适配了,记录一下适配的过程。

1.Manifest添加Android13文件读写适配:

<!--存储图像或者视频权限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    tools:ignore="ScopedStorage" android:maxSdkVersion="32"/>

<!--录制音频权限-->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>

2.Android13文件读写权限请求:

private void requestBasicPermission() {
    final RxPermissions rxPermissions = new RxPermissions(this);
    StringBuilder rationaleSb = new StringBuilder();
    StringBuilder deniedSb = new StringBuilder();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        permissions = new String[]{
                Manifest.permission.READ_MEDIA_AUDIO,
                Manifest.permission.READ_MEDIA_VIDEO,
                Manifest.permission.READ_MEDIA_IMAGES,
                Manifest.permission.RECORD_AUDIO,
        };
    } else {
        permissions = new String[]{
                Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE,};
    }
    rxPermissions.requestEach(permissions).subscribe(new Observer<Permission>() {
        @Override
        public void onSubscribe(@NonNull Disposable d) {
        }

        @Override
        public void onNext(@NonNull Permission permission) {
            if (permission.granted) {
                // 用户已经同意该权限
                Log.d(TAG, "权限:" + permission.name + " 已开启");
            } else if (permission.shouldShowRequestPermissionRationale) {
                // 用户拒绝了该权限,没有选中『不再询问』(Never ask again),那么下次再次启动时。还会提示请求权限的对话框
                Log.d(TAG, "权限:" + permission.name + " 权限拒绝,但没有选中 不再询问");
                if (rationaleSb.toString().contains(StringUtils.getPermissionName(MainActivity.this,permission.name))) {
                    return;
                }
                rationaleSb.append(StringUtils.getPermissionName(MainActivity.this,permission.name));
                rationaleSb.append("、");
            } else {
                // 用户拒绝了该权限,而且选中『不再询问』
                Log.d(TAG, "权限:" + permission.name + " 权限拒绝,并且选中 不再询问");
                if (deniedSb.toString().contains(StringUtils.getPermissionName(MainActivity.this,permission.name))) {
                    return;
                }
                deniedSb.append(StringUtils.getPermissionName(MainActivity.this,permission.name));
                deniedSb.append("、");
            }
        }

        @Override
        public void onError(@NonNull Throwable e) {
            Log.d(TAG, "permission  onError");
        }

        @Override
        public void onComplete() {
            if (TextUtils.isEmpty(rationaleSb) && TextUtils.isEmpty(deniedSb)) {
                Log.d(TAG, "permission.name ,权限已经允许");
                startAudioRecord();
            } else {
                if (!TextUtils.isEmpty(deniedSb)) {
                    showTipDialog(deniedSb, 0);
                } else if (!TextUtils.isEmpty(rationaleSb)) {
                    showTipDialog(rationaleSb, 1);
                }
            }
        }
    });
}

在这里插入图片描述

3.权限请求弹框:

    private void showPermissionDialog(StringBuilder permissionName, int permissionType) {
        if (null != mPermissionDialog && mPermissionDialog.isShowing()) {
            mPermissionDialog.dismiss();
            mPermissionDialog = null;
        }
        if (0 == permissionType) {
            mPermissionDialog = new PermissionDialog(MainActivity.this,
                    "请授权相关权限以确保相关功能能正常运行:" + permissionName
                            .toString().substring(0, permissionName.length() - 1), PermissionDialog.BUTTON_RIGHT_FLAG,
                    "确定", "知道了",
                    null, this::startAudioRecord);
            mPermissionDialog.show();
        } else if (1 == permissionType) {
            mPermissionDialog = new PermissionDialog(MainActivity.this,
                    "请授权相关权限以确保相关功能能正常运行:" + permissionName
                            .toString().substring(0, permissionName.length() - 1), PermissionDialog.BUTTON_RIGHT_FLAG,
                    "取消", "知道了",
                    null, this::startAudioRecord);
            mPermissionDialog.show();
        }
    }

4.完整的权限请求dialog类:

package com.example.audiorecorddemo.dialog;

import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.Window;
import android.widget.TextView;

import com.example.audiorecorddemo.R;


/**
 * 权限请求弹框
 */
public class PermissionDialog extends Dialog implements View.OnClickListener {
    private TextView mTvTitle, mTvTip, mTvLeftBtn, mTvRightBtn;
    private View mLineTip;

    private Window mWindow;

    private String mTileStr, mTipStr;
    private int mBtnSelectType;
    private String mLeftStr, mRightStr;

    private DialogTipLeftClickListener mLeftClickListener;
    private DialogTipRightClickListener mRightClickListener;
    // 左右button显示
    public static final int BUTTON_BOTH_FLAG = 1;
    // 仅左button显示
    public static final int BUTTON_LEFT_FLAG = 2;
    // 仅右button显示
    public static final int BUTTON_RIGHT_FLAG = 3;


    private boolean needShowCheck = false;

    public PermissionDialog(Context context, String titleStr, String tipStr, int buttonType, String leftStr,
                            String rightStr, DialogTipLeftClickListener leftClickListener,
                            DialogTipRightClickListener rightClickListener) {
        super(context);
        mWindow = getWindow();
        mTileStr = titleStr;
        mTipStr = tipStr;
        mBtnSelectType = buttonType;
        mLeftStr = leftStr;
        mRightStr = rightStr;
        mLeftClickListener = leftClickListener;
        mRightClickListener = rightClickListener;
    }

    public PermissionDialog(Context context, String tipStr, int buttonType, String leftStr,
                            String rightStr, DialogTipLeftClickListener leftClickListener,
                            DialogTipRightClickListener rightClickListener) {
        this(context, "", tipStr, buttonType, leftStr, rightStr, leftClickListener, rightClickListener);
    }

    public PermissionDialog(Context context, String tipStr, String leftStr, String rightStr,
                            DialogTipLeftClickListener leftClickListener,
                            DialogTipRightClickListener rightClickListener) {
        this(context, "", tipStr, BUTTON_BOTH_FLAG, leftStr, rightStr, leftClickListener, rightClickListener);
    }

    public PermissionDialog(Context context, String tipStr, String rightStr,
                            DialogTipLeftClickListener leftClickListener,
                            DialogTipRightClickListener rightClickListener) {
        this(context, "", tipStr, BUTTON_BOTH_FLAG, "", rightStr, leftClickListener, rightClickListener);
    }

    public PermissionDialog(Context context, String tipStr, DialogTipLeftClickListener leftClickListener,
                            DialogTipRightClickListener rightClickListener) {
        this(context, "", tipStr, BUTTON_BOTH_FLAG, "", "", leftClickListener, rightClickListener);
    }

    public PermissionDialog(Context context, String titleStr, String tipStr, int buttonType, String leftStr,
                            String rightStr, DialogTipLeftClickListener leftClickListener,
                            DialogTipRightClickListener rightClickListener, boolean needShowCheck) {
        this(context, titleStr, tipStr, buttonType, leftStr, rightStr, leftClickListener, rightClickListener);
        this.needShowCheck = needShowCheck;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_permiss_dialog);
        setCancelable(false);
        initView();
    }

    private void initView() {
        mTvTitle = findViewById(R.id.tv_dialog_corner_title);
        mTvTip = findViewById(R.id.tv_dialog_corner_tip);
        mLineTip = findViewById(R.id.view_dialog_corner_line);
        mTvLeftBtn = findViewById(R.id.tv_dialog_corner_left);
        mTvRightBtn = findViewById(R.id.tv_dialog_corner_right);


        // 初始化监听
        initListener();
        // 初始化数据
        initData();
    }

    private void initData() {
        // title
        if (!TextUtils.isEmpty(mTileStr)) {
            mTvTitle.setText(mTileStr);
            mTvTitle.setVisibility(View.VISIBLE);
        } else {
            mTvTitle.setVisibility(View.GONE);
        }
        // 提示
        if (!TextUtils.isEmpty(mTipStr)) {
            mTvTip.setText(mTipStr);
            mTvTip.setVisibility(View.VISIBLE);
        } else {
            mTvTip.setVisibility(View.GONE);
        }
        // 左边按钮
        if (!TextUtils.isEmpty(mLeftStr)) {
            mTvLeftBtn.setText(mLeftStr);
        }
        // 右边按钮
        if (!TextUtils.isEmpty(mRightStr)) {
            mTvRightBtn.setText(mRightStr);
        }
        // 按钮状态
        setButtonSelect(mBtnSelectType);

    }

    private void initListener() {
        mTvLeftBtn.setOnClickListener(this);
        mTvRightBtn.setOnClickListener(this);
    }

    private void setButtonSelect(int selectType) {
        if (mTvLeftBtn == null || mTvRightBtn == null || mLineTip == null) {
            return;
        }
        switch (selectType) {
            case BUTTON_LEFT_FLAG:
                mTvLeftBtn.setVisibility(View.VISIBLE);
                mTvRightBtn.setVisibility(View.GONE);
                mLineTip.setVisibility(View.GONE);
                break;
            case BUTTON_RIGHT_FLAG:
                mTvLeftBtn.setVisibility(View.GONE);
                mTvRightBtn.setVisibility(View.VISIBLE);
                mLineTip.setVisibility(View.GONE);
                break;
            case BUTTON_BOTH_FLAG:
            default:
                mTvLeftBtn.setVisibility(View.VISIBLE);
                mTvRightBtn.setVisibility(View.VISIBLE);
                mLineTip.setVisibility(View.VISIBLE);
                break;
        }
    }

    @Override
    public void onClick(View view) {
        int viewId = view.getId();
        if (R.id.tv_dialog_corner_left == viewId) {
            if (mLeftClickListener != null) {
                mLeftClickListener.onTipLeftClick();
            }
            if (isShowing()) {
                dismiss();
            }
        } else if (R.id.tv_dialog_corner_right == viewId) {
            if (mRightClickListener != null) {
                mRightClickListener.onTipRightClick();
            }
            if (isShowing()) {
                dismiss();
            }
        }
    }

    public interface DialogTipLeftClickListener {
        void onTipLeftClick();
    }

    public interface DialogTipRightClickListener {
        void onTipRightClick();
    }
}

5.文件管理类:

package com.example.audiorecorddemo.utils;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.NonNull;

import com.example.audiorecorddemo.app.MyApp;

import java.io.File;

/**
 * @author: njb
 * @date: 2023/8/15 17:13
 * @desc:
 */
public class FileManager {
    // 媒体模块根目录
    private static final String SAVE_MEDIA_ROOT_DIR = Environment.DIRECTORY_DCIM;
    // 媒体模块存储路径
    private static final String SAVE_MEDIA_DIR = SAVE_MEDIA_ROOT_DIR + "/RecordManager";
    private static final String SAVE_MEDIA_PHOTO_DIR = SAVE_MEDIA_DIR + "/photo";
    private static final String SAVE_MEDIA_AUDIO_DIR = SAVE_MEDIA_DIR + "/audio";
    private static final String SAVE_MEDIA_VIDEO_DIR = SAVE_MEDIA_DIR + "/video";
    // JPG后缀
    public static final String JPG_SUFFIX = ".jpg";
    // PNG后缀
    public static final String PNG_SUFFIX = ".png";
    // MP4后缀
    public static final String MP4_SUFFIX = ".mp4";

    /**
     * 保存图片到系统相册
     *
     * @param context
     * @param file
     */
    public static String saveImage(Context context, File file) {
        ContentResolver localContentResolver = context.getContentResolver();
        ContentValues localContentValues = getImageContentValues(context, file, System.currentTimeMillis());
        localContentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, localContentValues);

        Intent localIntent = new Intent("android.intent.action.MEDIA_SCANNER_SCAN_FILE");
        final Uri localUri = Uri.fromFile(file);
        localIntent.setData(localUri);
        context.sendBroadcast(localIntent);
        return file.getAbsolutePath();
    }

    public static ContentValues getImageContentValues(Context paramContext, File paramFile, long paramLong) {
        ContentValues localContentValues = new ContentValues();
        localContentValues.put("title", paramFile.getName());
        localContentValues.put("_display_name", paramFile.getName());
        localContentValues.put("mime_type", "image/jpeg");
        localContentValues.put("datetaken", Long.valueOf(paramLong));
        localContentValues.put("date_modified", Long.valueOf(paramLong));
        localContentValues.put("date_added", Long.valueOf(paramLong));
        localContentValues.put("orientation", Integer.valueOf(0));
        localContentValues.put("_data", paramFile.getAbsolutePath());
        localContentValues.put("_size", Long.valueOf(paramFile.length()));
        return localContentValues;
    }

    /**
     * 获取App存储根目录
     */
    public static String getAppRootDir() {
        String path = getStorageRootDir();
        FileUtil.createOrExistsDir(path);
        return path;
    }

    /**
     * 获取文件存储根目录
     */
    public static String getStorageRootDir() {
        File filePath = MyApp.getInstance().getExternalFilesDir("");
        String path;
        if (filePath != null) {
            path = filePath.getAbsolutePath();
        } else {
            path = MyApp.getInstance().getFilesDir().getAbsolutePath();
        }
        return path;
    }

    /**
     * 图片地址
     */
    public static String getCameraPhotoPath() {
        return getFolderDirPath(SAVE_MEDIA_PHOTO_DIR);
    }



    /**
     * 视频地址
     */
    public static String getCameraVideoPath() {
        return getFolderDirPath(SAVE_MEDIA_VIDEO_DIR);
    }

    public static String getCameraAudioPath() {
        return getSaveDir(SAVE_MEDIA_AUDIO_DIR);
    }

    public static String getFolderDirPath(String dstDirPathToCreate) {
        File dstFileDir = new File(Environment.getExternalStorageDirectory(), dstDirPathToCreate);
        if (!dstFileDir.exists() && !dstFileDir.mkdirs()) {
            Log.e("Failed to create file", dstDirPathToCreate);
            return null;
        }
        return dstFileDir.getAbsolutePath();
    }

    /**
     * 获取具体模块存储目录
     */
    public static String getSaveDir(@NonNull String directory) {
        String path = "";
        if (TextUtils.isEmpty(directory) || "/".equals(directory)) {
            path = "";
        } else if (directory.startsWith("/")) {
            path = directory;
        } else {
            path = "/" + directory;
        }
        path = getAppRootDir() + path;
        FileUtil.createOrExistsDir(path);
        return path;
    }
}

6.录音方法:

主要就是文件的生成和创建,由于Android10以后不能随意创建私有文件,所以生成的audio文件放到系统的DCIM、MUSIC、Download目录下,并且是项目自己包名下:

在这里插入图片描述

public void startRecord(WeakReference<Context> weakReference) {
    this.weakReference = weakReference;
    LogUtils.e(TAG, "开始录音");
    //生成PCM文件
    String fileName = DateFormat.format("yyyy-MMdd-HHmmss", Calendar.getInstance(Locale.getDefault())) + ".pcm";
    File file = new File(FileManager.getCameraAudioPath(), "/ACC音频/");
    if (!file.exists()) {
        file.mkdir();
    }
    String audioSaveDir = file.getAbsolutePath();
    LogUtils.e(TAG, audioSaveDir);
    recordFile = new File(audioSaveDir, fileName);
    LogUtils.e(TAG, "生成文件" + recordFile);
    //如果存在,就先删除再创建
    if (recordFile.exists()) {
        recordFile.delete();
        LogUtils.e(TAG, "删除文件");
    }
    try {
        recordFile.createNewFile();
        LogUtils.e(TAG, "创建文件");
    } catch (IOException e) {
        LogUtils.e(TAG, "未能创建");
        throw new IllegalStateException("未能创建" + recordFile.toString());
    }
    if (filePathList.size() == 2) {
        filePathList.clear();
    }
    filePathList.add(recordFile);
    try {
        //输出流
        OutputStream os = new FileOutputStream(recordFile);
        BufferedOutputStream bos = new BufferedOutputStream(os);
        DataOutputStream dos = new DataOutputStream(bos);
        int bufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, AudioFormat.CHANNEL_IN_STEREO, audioEncoding);
        if (ActivityCompat.checkSelfPermission(weakReference.get(), Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {

            return;
        }
       audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, AudioFormat.CHANNEL_IN_STEREO, audioEncoding, bufferSize);

        short[] buffer = new short[bufferSize];
        audioRecord.startRecording();
        LogUtils.e(TAG, "开始录音");
        isRecording = true;
        while (isRecording) {
            int bufferReadResult = audioRecord.read(buffer, 0, bufferSize);
            for (int i = 0; i < bufferReadResult; i++) {
                dos.writeShort(buffer[i]);
            }
        }
        audioRecord.stop();
        dos.close();
    } catch (Exception e) {
        e.printStackTrace();
        LogUtils.e(TAG, "录音失败");
        showToast("录音失败");
    }
}

7.启动录音:

btnRecord.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        buttonEnabled(false, true, true);
        startAudioRecord();
    }
});

8.停止录音:

btnStop.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        new Handler().post(new Runnable() {
            @Override
            public void run() {
                buttonEnabled(true, true, false);
                mHandler.post(() -> Toast.makeText(MainActivity.this, "停止录音", Toast.LENGTH_LONG).show());
                AudioManagerUtils.getInstance().pauseAudio();
            }
        });
    }
});

9.开始播放:

btnPlay.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        AudioManagerUtils.getInstance().playPcm(true);
        buttonEnabled(false, false, true);
    }
});

10.完整的测试代码:

package com.example.audiorecorddemo;

import android.Manifest;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import com.blankj.utilcode.util.LogUtils;
import com.example.audiorecorddemo.dialog.PermissionDialog;
import com.example.audiorecorddemo.utils.AudioManagerUtils;
import com.example.audiorecorddemo.utils.StringUtils;
import com.tbruyelle.rxpermissions3.Permission;
import com.tbruyelle.rxpermissions3.RxPermissions;

import java.lang.ref.WeakReference;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;

public class MainActivity extends AppCompatActivity {
    private Button btnRecord, btnPlay, btnStop;
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            3, 5,
            1, TimeUnit.MINUTES,
            new LinkedBlockingDeque<>(10),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());
    private String[] permissions = null;
    private PermissionDialog mPermissionDialog = null;
    /**
     * 被用户拒绝的权限列表
     */
    private static final int MY_PERMISSIONS_REQUEST = 1001;
    private final String TAG = MainActivity.this.getClass().getSimpleName();
    private Handler mHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        requestBasicPermission();
        initView();
    }

    private void requestBasicPermission() {
        final RxPermissions rxPermissions = new RxPermissions(this);
        StringBuilder rationaleSb = new StringBuilder();
        StringBuilder deniedSb = new StringBuilder();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            permissions = new String[]{
                    Manifest.permission.READ_MEDIA_AUDIO,
                    Manifest.permission.READ_MEDIA_VIDEO,
                    Manifest.permission.READ_MEDIA_IMAGES,
                    Manifest.permission.RECORD_AUDIO,
            };
        } else {
            permissions = new String[]{
                    Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE,};
        }
        rxPermissions.requestEach(permissions).subscribe(new Observer<Permission>() {
            @Override
            public void onSubscribe(@NonNull Disposable d) {
            }

            @Override
            public void onNext(@NonNull Permission permission) {
                if (permission.granted) {
                    LogUtils.d(TAG, "权限:" + permission.name + " 已开启");
                } else if (permission.shouldShowRequestPermissionRationale) {
                    LogUtils.d(TAG, "权限:" + permission.name + " 权限拒绝,但没有选中 不再询问");
                    if (rationaleSb.toString().contains(StringUtils.getPermissionName(MainActivity.this,permission.name))) {
                        return;
                    }
                    rationaleSb.append(StringUtils.getPermissionName(MainActivity.this,permission.name));
                    rationaleSb.append("、");
                } else {
                    LogUtils.d(TAG, "权限:" + permission.name + " 权限拒绝,并且选中 不再询问");
                    if (deniedSb.toString().contains(StringUtils.getPermissionName(MainActivity.this,permission.name))) {
                        return;
                    }
                    deniedSb.append(StringUtils.getPermissionName(MainActivity.this,permission.name));
                    deniedSb.append("、");
                }
            }

            @Override
            public void onError(@NonNull Throwable e) {
                LogUtils.d(TAG, "permission  onError");
            }

            @Override
            public void onComplete() {
                if (TextUtils.isEmpty(rationaleSb) && TextUtils.isEmpty(deniedSb)) {
                    LogUtils.d(TAG, "permission.name ,权限已经允许");
                    startAudioRecord();
                } else {
                    if (!TextUtils.isEmpty(deniedSb)) {
                        showPermissionDialog(deniedSb, 0);
                    } else if (!TextUtils.isEmpty(rationaleSb)) {
                        showPermissionDialog(rationaleSb, 1);
                    }
                }
            }
        });
    }

    private void showPermissionDialog(StringBuilder permissionName, int permissionType) {
        if (null != mPermissionDialog && mPermissionDialog.isShowing()) {
            mPermissionDialog.dismiss();
            mPermissionDialog = null;
        }
        if (0 == permissionType) {
            mPermissionDialog = new PermissionDialog(MainActivity.this,
                    "请授权相关权限以确保相关功能能正常运行:" + permissionName
                            .toString().substring(0, permissionName.length() - 1), PermissionDialog.BUTTON_RIGHT_FLAG,
                    "确定", "知道了",
                    null, this::startAudioRecord);
            mPermissionDialog.show();
        } else if (1 == permissionType) {
            mPermissionDialog = new PermissionDialog(MainActivity.this,
                    "请授权相关权限以确保相关功能能正常运行:" + permissionName
                            .toString().substring(0, permissionName.length() - 1), PermissionDialog.BUTTON_RIGHT_FLAG,
                    "取消", "知道了",
                    null, this::startAudioRecord);
            mPermissionDialog.show();
        }
    }

    private void initView() {
        btnRecord = findViewById(R.id.btn_record);
        btnPlay = findViewById(R.id.btn_play);
        btnStop = findViewById(R.id.btn_stop);

        btnRecord.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                buttonEnabled(false, true, true);
                startAudioRecord();
            }
        });

        btnPlay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AudioManagerUtils.getInstance().playPcm(true);
                buttonEnabled(false, false, true);
            }
        });
        btnStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        buttonEnabled(true, true, false);
                        mHandler.post(() -> Toast.makeText(MainActivity.this, "停止录音", Toast.LENGTH_LONG).show());
                        AudioManagerUtils.getInstance().pauseAudio();
                    }
                });
            }
        });
    }

    private void startAudioRecord() {
        threadPoolExecutor.execute(() -> {
            mHandler.post(() -> Toast.makeText(MainActivity.this, "开始录音", Toast.LENGTH_LONG).show());
            AudioManagerUtils.getInstance().startRecord(new WeakReference<>(getApplicationContext()));
        });
    }

    private void buttonEnabled(boolean record, boolean play, boolean stop) {
        btnRecord.setEnabled(record);
        btnPlay.setEnabled(play);
        btnStop.setEnabled(stop);
    }

    @Override
    protected void onStop() {
        super.onStop();
        AudioManagerUtils.getInstance().pauseAudio();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        AudioManagerUtils.getInstance().releaseAudio();
    }

}

11.实现的效果如下:

在这里插入图片描述

12.总结:

  • 以上就是今天的内容,录制音频时适配Android13.
  • Android13文件读写细分为三个权限 READ_MEDIA_AUDIO、READ_MEDIA_VIDEO、READ_MEDIA_IMAGES.
  • Android10以上文件创建和生成需要在公共目录,不能随意创建和读写.

13.项目的源码地址:

https://gitee.com/jackning_admin/audio-record-demo-a

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/258180.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用docker-compose搭建docker私服与配置WebUI

简介 本文介绍了使用docker compose 搭建 docker私服 环境 Docker version 24.0.6, build ed223bc Docker Compose version v2.21.0 正文 一、创建registry文件夹 我的路径是/usr/loca/docker/registry 二、创建并编写docker-compose.yml version: "3.9" services…

《深入理解计算机系统》学习笔记 - 第六课 - 机器级别的程序二

Lecture 06 Machine Level Programming II Control 机器级别程序控制二 文章目录 Lecture 06 Machine Level Programming II Control 机器级别程序控制二处理器的状态条件码&#xff08;隐式设置&#xff09;通过算术运算隐式设置条件码(将其视为副作用)通过cmp比较命令显示的设…

关于EasyExcel 合并单元格方法该如何实现

在做一个业务的导出&#xff0c;目前遇到一个需求&#xff0c;如下图&#xff1a; import com.alibaba.excel.metadata.CellData; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.write.handler.CellWriteHandler; import com.alibaba.excel.write.metad…

【Windbg】学习及在CTF中解题

1、Windbg介绍 Windbg是一款Windows强大的调试器&#xff0c;可以调试0和3环的程序。 在实际开发中&#xff0c;可以调试我们的错误程序&#xff0c;从而定位关键代码&#xff0c;进行程序代码修复。 WinDbg 是一种调试器工具&#xff0c;由微软公司开发&#xff0c;用于分析…

Springboot数据校验与异常篇

一、异常处理 1.1Http状态码 HTTP状态码是指在HTTP通信过程中&#xff0c;服务器向客户端返回的响应状态。它通过3位数字构成&#xff0c;第一个数字定义了响应的类别&#xff0c;后两位数字没有具体分类作用。以下是常见的HTTP状态码及其含义&#xff1a; - 1xx&#xff08;信…

C#二甲医院实验室信息系统源码

医院实验室信息系统简称(Hospitallaboratoryinformationsystem)&#xff0c;也可以称作实验室&#xff08;检验科&#xff09;信息系统或者LIS系统。 LIS定义 其主要功能是将检验的实验仪器传出的检验数据经分析后&#xff0c;自动生成打印报告&#xff0c;通过网络存储在数据…

STM32能够做到数据采集和发送同时进行吗?

STM32能够做到数据采集和发送同时进行吗&#xff1f; 在开始前我有一些资料&#xff0c;是我根据自己从业十年经验&#xff0c;熬夜搞了几个通宵&#xff0c;精心整理了一份「STM32的资料从专业入门到高级教程工具包」&#xff0c;点个关注&#xff0c;全部无偿共享给大家&…

2023ChatGPT浪潮,2024开源大语言模型会成王者?

《2023ChatGPT浪潮&#xff0c;2024开源大语言模型会成王者&#xff1f;》 一、2023年的回顾 1.1、背景 我们正迈向2023年的终点&#xff0c;回首这一年&#xff0c;技术行业的发展如同车轮滚滚。尽管互联网行业在最近几天基本上处于冬天&#xff0c;但在这一年间我们仍然经…

Spring Boot + MinIO 实现文件切片极速上传技术

文章目录 1. 引言2. 文件切片上传简介3. 技术选型3.1 Spring Boot3.2 MinIO 4. 搭建Spring Boot项目5. 集成MinIO5.1 配置MinIO连接信息5.2 MinIO配置类 6. 文件切片上传实现6.1 控制器层6.2 服务层6.3 文件切片上传逻辑 7. 文件合并逻辑8. 页面展示9. 性能优化与拓展9.1 性能优…

新手入门linux介绍以及 简单命令

一.分区 / 根 必须要有&#xff0c;linux系统最开始的地方&#xff0c;linux系统的唯一入口 、boot 开机启动项&#xff0c;开机的启动文件存放的位置 swap 交换分区&#xff0c;将硬盘上的一部分空间作为内存使用&#xff0c;一般是内存的两倍 root 超级管理员用户&#x…

智能优化算法应用:基于未来搜索算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于未来搜索算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于未来搜索算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.未来搜索算法4.实验参数设定5.算法结果6.…

Python导入模块,Python import用法(超级详细)

对于一个真实的 Python 程序&#xff0c;我们不可能自己完成所有的工作&#xff0c;通常都需要借助于第三方类库。此外&#xff0c;也不可能在一个源文件中编写整个程序的源代码&#xff0c;这些都需要以模块化的方式来组织项目的源代码。 使用 import 导入模块的语法&#xf…

护眼灯对眼睛有好处吗?学生备考台灯分享

孩子的身心健康&#xff0c;永远是作为家长最心的事情&#xff0c;但是现在的青少年近视率如此高的情况下&#xff0c;又应该如何才能更好的保护眼睛呢&#xff1f;也因为这个问题所以才有了现在学生都必备的护眼台灯。护眼台灯相对于传统台灯来说光源更安全&#xff0c;有效保…

LVS最终奥义之DR直接路由模式

1 LVS-DR(直接路由模式) 1.1 LVS-DR模式工作过程 1.客户端通过VIP将访问请求报文&#xff08;源IP为客户端IP&#xff0c;目标IP为VIP&#xff09;发送到调度器 2.调度器通过调度算法选择最适合的节点服务器并重新封装数据报文&#xff08;将源mac地址改为调度器的mac地址&am…

第80讲:GTID全局事务标识符的基本概念以及在Binlog中应用GTID

文章目录 1.GTID的基本概念1.1.为什么要引入GTID1.2.什么是GTID 2.开启GTID全局事务标识符的功能3.模拟产生Binlog日志观察开启GTID功能的区别3.1.模拟产生Binlog日志3.2.观察Binlog日志中的事件信息3.2.观察节点状态有什么变化3.3.观察Binlog日志会有什么变化 4.使用GTID来截取…

计算机基础,以及实施运维工程师介绍

目录 一.实施&#xff0c;运维工程师介绍 1.什么是实施工程师&#xff1f; 实施工程师职责 2.什么是运维工程师&#xff1f; 运维工程师职责 3.实施运维需要的技术 数据库 操作系统 网络 服务器 软件 硬件 网络 二.计算机介绍 CPU 存储器 io 总线 主板 三.操…

韧性生长 共话未来|艾诗、罗曼诺在第二届广州国际品牌节喜获两项殊荣

韧性生长 共话未来&#xff5c;维布络集团旗下品牌艾诗、罗曼诺在第二届广州国际品牌节喜获两项殊荣 12月15日&#xff0c;在广东省市场监督管理局、广州市市场监督管理局、广州市商务局、中国广告协会、中国出版集团东方出版中心的指导下&#xff0c;由广州国际品牌节组委会主…

Tomcat为什么要重写类加载器?

文章目录 一、双亲委派机制二、分析1、Tomcat需要隔离性2、Tomcat需要热替换3、打破双亲委派机制 三、Tomcat类加载器1、拓展类加载器2、工作原理 四、总结 一、双亲委派机制 首先了解下双亲委派机制&#xff0c;大致过程如下&#xff1a; 简单来说&#xff0c;就是加载class…

Vue的脚手架

脚手架配置 脚手架文档&#xff1a;Vue CLI npm config set registry https://registry.npm.taobao.org vue.config.js配置选项&#xff1a; 配置参考 | Vue CLI ref选项 ref和id类似&#xff0c;给标签打标识。 document.getElementById(btn); this.$ref.btn; 父子组…

深度学习或机器学习的模型部署相关的初步了解及分析

机器学习-深度学习 部署相关资料文档 这是上班之后的第一个文档&#xff0c;由于项目原因需要去了解一些和模型部署相关的知识&#xff0c;所以就简单了解了一下相应的部署引擎和框架&#xff0c;也是刚刚开始学习&#xff0c;如果有什么写的不对的欢迎大家交流&#xff0c;看…