Android13音頻錄制適配
前言:
之前寫過一篇音頻錄制的文章,當(dāng)時(shí)是在Android10以下的手機(jī)可以成功錄制和播放,但是Android10及以上手機(jī)提示創(chuàng)建文件失敗,最近做過Android13的適配,索性一起把之前的錄音也適配了,記錄一下適配的過程。
1.Manifest添加Android13文件讀寫適配:
<!--存儲(chǔ)圖像或者視頻權(quán)限-->
<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"/>
<!--錄制音頻權(quán)限-->
<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文件讀寫權(quán)限請(qǐng)求:
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) {
// 用戶已經(jīng)同意該權(quán)限
Log.d(TAG, "權(quán)限:" + permission.name + " 已開啟");
} else if (permission.shouldShowRequestPermissionRationale) {
// 用戶拒絕了該權(quán)限,沒有選中『不再詢問』(Never ask again),那么下次再次啟動(dòng)時(shí)。還會(huì)提示請(qǐng)求權(quán)限的對(duì)話框
Log.d(TAG, "權(quán)限:" + permission.name + " 權(quán)限拒絕,但沒有選中 不再詢問");
if (rationaleSb.toString().contains(StringUtils.getPermissionName(MainActivity.this,permission.name))) {
return;
}
rationaleSb.append(StringUtils.getPermissionName(MainActivity.this,permission.name));
rationaleSb.append("、");
} else {
// 用戶拒絕了該權(quán)限,而且選中『不再詢問』
Log.d(TAG, "權(quán)限:" + permission.name + " 權(quán)限拒絕,并且選中 不再詢問");
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 ,權(quán)限已經(jīng)允許");
startAudioRecord();
} else {
if (!TextUtils.isEmpty(deniedSb)) {
showTipDialog(deniedSb, 0);
} else if (!TextUtils.isEmpty(rationaleSb)) {
showTipDialog(rationaleSb, 1);
}
}
}
});
}
3.權(quán)限請(qǐng)求彈框:
private void showPermissionDialog(StringBuilder permissionName, int permissionType) {
if (null != mPermissionDialog && mPermissionDialog.isShowing()) {
mPermissionDialog.dismiss();
mPermissionDialog = null;
}
if (0 == permissionType) {
mPermissionDialog = new PermissionDialog(MainActivity.this,
"請(qǐng)授權(quán)相關(guān)權(quán)限以確保相關(guān)功能能正常運(yùn)行:" + 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,
"請(qǐng)授權(quán)相關(guān)權(quán)限以確保相關(guān)功能能正常運(yùn)行:" + permissionName
.toString().substring(0, permissionName.length() - 1), PermissionDialog.BUTTON_RIGHT_FLAG,
"取消", "知道了",
null, this::startAudioRecord);
mPermissionDialog.show();
}
}
4.完整的權(quán)限請(qǐng)求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;
/**
* 權(quán)限請(qǐng)求彈框
*/
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);
// 初始化監(jiān)聽
initListener();
// 初始化數(shù)據(jù)
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);
}
// 按鈕狀態(tài)
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;
// 媒體模塊存儲(chǔ)路徑
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";
/**
* 保存圖片到系統(tǒng)相冊
*
* @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存儲(chǔ)根目錄
*/
public static String getAppRootDir() {
String path = getStorageRootDir();
FileUtil.createOrExistsDir(path);
return path;
}
/**
* 獲取文件存儲(chǔ)根目錄
*/
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();
}
/**
* 獲取具體模塊存儲(chǔ)目錄
*/
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.錄音方法:
主要就是文件的生成和創(chuàng)建,由于Android10以后不能隨意創(chuàng)建私有文件,所以生成的audio文件放到系統(tǒng)的DCIM、MUSIC、Download目錄下,并且是項(xiàng)目自己包名下:
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);
//如果存在,就先刪除再創(chuàng)建
if (recordFile.exists()) {
recordFile.delete();
LogUtils.e(TAG, "刪除文件");
}
try {
recordFile.createNewFile();
LogUtils.e(TAG, "創(chuàng)建文件");
} catch (IOException e) {
LogUtils.e(TAG, "未能創(chuàng)建");
throw new IllegalStateException("未能創(chuàng)建" + 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.啟動(dòng)錄音:
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;
/**
* 被用戶拒絕的權(quán)限列表
*/
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, "權(quán)限:" + permission.name + " 已開啟");
} else if (permission.shouldShowRequestPermissionRationale) {
LogUtils.d(TAG, "權(quán)限:" + permission.name + " 權(quán)限拒絕,但沒有選中 不再詢問");
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, "權(quán)限:" + permission.name + " 權(quán)限拒絕,并且選中 不再詢問");
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 ,權(quán)限已經(jīng)允許");
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,
"請(qǐng)授權(quán)相關(guān)權(quán)限以確保相關(guān)功能能正常運(yùn)行:" + 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,
"請(qǐng)授權(quán)相關(guān)權(quán)限以確保相關(guān)功能能正常運(yùn)行:" + 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.實(shí)現(xiàn)的效果如下:
文章來源:http://www.zghlxwxcb.cn/news/detail-831396.html
12.總結(jié):
- 以上就是今天的內(nèi)容,錄制音頻時(shí)適配Android13.
- Android13文件讀寫細(xì)分為三個(gè)權(quán)限 READ_MEDIA_AUDIO、READ_MEDIA_VIDEO、READ_MEDIA_IMAGES.
- Android10以上文件創(chuàng)建和生成需要在公共目錄,不能隨意創(chuàng)建和讀寫.
13.項(xiàng)目的源碼地址:
https://gitee.com/jackning_admin/audio-record-demo-a文章來源地址http://www.zghlxwxcb.cn/news/detail-831396.html
到了這里,關(guān)于Android13音頻錄制適配的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!