Android13音频录制适配

news2024/11/24 2:56:26

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL】:表的内外连接

表的内外连接 一.内连接二.外连接1.左外连接2.右外连接 一.内连接 内连接实际上就是利用where子句对两种表形成的笛卡儿积进行筛选&#xff0c;我们前面学习的查询都是内连接&#xff0c;也是在开发过程中使用的最多的连接查询。 语法&#xff1a;select 字段 from 表1 inner …

如何通过浏览器扩展修改浏览器的UserAgent

User-Agent Switcher and Manage&#xff1a; https://chromewebstore.google.com/detail/user-agent-switcher-and-m/bhchdcejhohfmigjafbampogmaanbfkg 使用说明&#xff1a; User Agent Switcher and Manager :: add0n.com 示例&#xff1a; 勾选一个UA&#xff0c;比如c…

测试工具Jmeter:设置中文界面

首先我们打开Jmeter所在的文件&#xff0c;进入bin目录&#xff0c;打开Jmeter.properties&#xff1a; 打开后找到languageen&#xff1a; 改为zh_CN: 保存关闭&#xff0c;然后再打开Jmeter&#xff1a; 英文并不会显得高级&#xff0c;能做到高效的性能测试才是高级的。

邮件营销:定义、优势与策略指南

什么是邮件营销&#xff1f;跨境电商或者出海企业可能会经常使用它&#xff0c;它是传统的营销方式之一&#xff0c;在上世纪80年年代得以运用。 邮件营销&#xff0c;英文全称为Email Direct Marketing&#xff0c;缩写为EDM。它是指在收件人许可的情况下&#xff0c;通过电子…

【经典LeetCode算法题目专栏分类】【第4期】BFS广度优先算法:单词接龙、最小基因变化、二进制矩阵中的最短路径

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能AI、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 一般涉及到最小层数问题…

用bash写脚本

本章主要介绍如何使用bash写脚本。 了解通配符 了解变量 了解返回值和数值运算 数值的对比 判断语句 循环语句 grep的用法是“grep 关键字 file”&#xff0c;意思是从file中过滤出含有关键字的行。 例如&#xff0c;grep root /var/log/messages&#xff0c;意思是从/var/log/…

家居家纺品牌网站建设的作用是什么

家居家纺企业往往规模很高&#xff0c;比如被褥、枕头床单等都是家家必备的&#xff0c;市场规模连年增长&#xff0c;但在实际经营中&#xff0c;随着互联网深入各个行业&#xff0c;传统企业线上化能力不足&#xff0c;也产生了许多痛点。 1、品牌传播难 家居家纺企业以批发…

PCL点云处理之点云置平(拟合平面绕中心旋转到绝对水平)(二百二十七)

PCL点云处理之点云置平(绕中心旋转到绝对水平)(二百二十七) 一、什么是点云置平二、算法流程三、算法实现一、什么是点云置平 有时候,我们处理的点云平面并非位于水平面,而是位于某个任一三维平面上,而大多数算法又只能在水平面处理,或者水平面的点云处理是相对更简单…

9.鸿蒙app用户界面的跳转abilityslice的跳转

9.用户界面的跳转abilityslice的跳转&#xff0c;值传递&#xff0c;数值累加 首页页面显示1&#xff0c;第2页显示2&#xff0c;再次点击返回首页3。。。 MainAbilitySlice.java 关键代码&#xff1a; 点击事件 text.setClickedListener(new Component.ClickedListener() …

[自动化运维工具]ansible简单介绍和常用模块

ansible 源操作主机功能 自动化运维&#xff08;playbook剧本yaml&#xff09; 是基于python开发的一个配置管理和应用部署工具&#xff0c;在自动化运维中&#xff0c;现在还是异军突起 ansible能批量配置&#xff0c;部署&#xff0c;管理上千台主机&#xff0c;类似于xshell…

安全生产隐患排查治理信息化系统软件

安全隐患排查系统实现对重大危险源企业、安全隐患信息的登记、整改、复查、分类和统计。系统涵盖了安全隐患排查整治工作的各项基本内容&#xff0c;实现以安全隐患排查整治业务流为主线&#xff0c;处理流程简洁清晰、快速灵活&#xff1b;以排查整治流程为干线&#xff0c;快…

5行Python实现验证码识别,太稳了

很久之前&#xff0c;分享过一次Python代码实现验证码识别的办法。 当时采用的是pillowpytesseract&#xff0c;优点是免费&#xff0c;较为易用。但其识别精度一般&#xff0c;若想要更高要求的验证码识别&#xff0c;初学者就只能去选择使用百度API接口了。 但其实百度API接…

Linux-----21、挂载

# 挂载命令 将硬件资源&#xff0c;或文件资源&#x1f4bf;&#xff0c;和&#x1f4c2;空目录&#x1f517;连接起来的过程 # mount linux 所有存储设备都必须挂载使用&#xff0c;包括硬盘 ​ 命令名称&#xff1a;mount ​ 命令所在路径&#xff1a;/bin/mount ​ 执行…

无懈可击的防泄密之旅:迅软DSE在民营银行的成功实践

客户简要介绍 某股份有限公司主体是中部地区的民营银行&#xff0c;由其母公司联合9家知名民营企业共同发起设立。正式开业于2016年&#xff0c;紧紧围绕目标产业生态圈和消费金融&#xff0c;着力打造产业银行、便捷银行、数字银行、财富管理银行为一体的BEST银行&#xff0c…

学习Java第74天,Ajax简介

什么是ajax AJAX Asynchronous JavaScript and XML&#xff08;异步的 JavaScript 和 XML&#xff09;。 AJAX 不是新的编程语言&#xff0c;而是一种使用现有标准的新方法。 AJAX 最大的优点是在不重新加载整个页面的情况下&#xff0c;可以与服务器交换数据并更新部分网页…

基于Java SSM框架实现二手车交易网站系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现二手车交易网站系统演示 摘要 二手车交易网站采用B/S模式&#xff0c;促进了二手车交易网站的安全、质量、快捷的发展。传统的管理模式还处于手工处理阶段&#xff0c;管理效率极低&#xff0c;随着用户的不断增多&#xff0c;传统基于手工管理模式已经无…

Redis7--基础篇9(SpringBoot集成Redis)

1. jedis、lettuce、Redistemplate的关系 第一代为jedis&#xff0c;之后推出了lettuce&#xff0c;然后springboot继承了Redistemplate&#xff0c;现推荐使用Redistemplate。 总的来说&#xff0c;jedis、lettuce、Redistemplate都是java操作Redis数据库的驱动。 2. 本地Ja…

MapReduce和Yarn部署+入门

看的黑马视频记的笔记 目录 1.入门知识点 分布式计算&#xff1a; 概念&#xff1a; 两种模式&#xff1a; MapReduce&#xff08;分布式计算&#xff0c;分散汇总模式&#xff09; 概念 执行原理 注&#xff1a; Yarn&#xff08;分布式资源调度&#xff09; 概述 Y…

探讨小鹏汽车CAN通讯协议分析破解过程数据研究技术应用

当前新能源电动汽车设计日益复杂&#xff0c;为提高舒适性、功能性、提升性能和确保更高的安全性&#xff0c;很多汽车的设计中融入了更复杂的功能。包括了雷达、激光雷达、自适应巡航、L2以上自动驾驶系统&#xff0c;高级驾驶辅助系统、盲区监测等等。安装在汽车上的传感器和…

代码魔法:递归嵌套的《迷宫之旅》算法解析

前言 在代码的舞台上&#xff0c;递归算法的奇迹就如同魔法一般令人叹为观止。本文以经典的迷宫问题为基础&#xff0c;通过递归嵌套的方式&#xff0c;带你踏上一场神奇的迷宫之旅。 迷宫规则 迷宫由一个二维数组表示&#xff0c;其中0表示可通行的路径&#xff0c;1表示墙…