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>() {@Overridepublic void onSubscribe(@NonNull Disposable d) {}@Overridepublic 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("、");}}@Overridepublic void onError(@NonNull Throwable e) {Log.d(TAG, "permission  onError");}@Overridepublic 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;}@Overrideprotected 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() {// titleif (!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;}}@Overridepublic 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() {@Overridepublic void onClick(View v) {buttonEnabled(false, true, true);startAudioRecord();}
});

8.停止录音:

btnStop.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {new Handler().post(new Runnable() {@Overridepublic 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() {@Overridepublic 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();@Overrideprotected 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>() {@Overridepublic void onSubscribe(@NonNull Disposable d) {}@Overridepublic 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("、");}}@Overridepublic void onError(@NonNull Throwable e) {LogUtils.d(TAG, "permission  onError");}@Overridepublic 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() {@Overridepublic void onClick(View v) {buttonEnabled(false, true, true);startAudioRecord();}});btnPlay.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {AudioManagerUtils.getInstance().playPcm(true);buttonEnabled(false, false, true);}});btnStop.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {new Handler().post(new Runnable() {@Overridepublic 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);}@Overrideprotected void onStop() {super.onStop();AudioManagerUtils.getInstance().pauseAudio();}@Overrideprotected 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.mzph.cn/news/237166.shtml

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

相关文章

Python 时间日期处理库函数

标准库 datetime >>> import datetime >>> date datetime.date(2023, 12, 20) >>> print(date) 2023-12-20 >>> date datetime.datetime(2023, 12, 20) >>> print(date) 2023-12-20 00:00:00 >>> print(date.strfti…

gem5 RubyPort: mem_request_port作用与连接 simple-MI_example.py

简介 回答这个问题&#xff1a;RubyPort的口下&#xff0c;一共定义了六个口&#xff0c;分别是mem_request_port&#xff0c;mem_response_port&#xff0c;pio_request_port&#xff0c;pio_response_port&#xff0c;in_ports, interrupt_out_ports&#xff0c;他们分别有什…

【异常】jdk21升级,asm报错Unsupported class file major version 65 springboot2 升级JDK21

【异常】jdk21升级&#xff0c;asm报错Unsupported class file major version 65 错误信息 Caused by: org.springframework.core.NestedIOException: ASM ClassReader failed to parse class file - probably due to a new Java class file version that isnt supported yet…

Java对接腾讯多人音视频房间示例

最近在对接腾讯的多人音视频房间&#xff0c;做一个类似于腾讯会议的工具&#xff0c;至于为什么不直接用腾讯会议&#xff0c;这个我也不知道&#xff0c;当然我也不敢问 首先是腾讯官方的文档地址&#xff1a;https://cloud.tencent.com/document/product/1690 我是后端所以…

CSS自适应分辨率 amfe-flexible 和 postcss-pxtorem:大屏高宽自适应问题

前言 继上篇《CSS自适应分辨率 amfe-flexible 和 postcss-pxtorem》。 发现一个有趣的问题&#xff0c;文件 rem.js 中按照宽度设置自适应&#xff0c;适用于大多数页面&#xff0c;但当遇到大屏就不那么合适了。 问题 使用宽度&#xff0c;注意代码第2 和 4 行&#xff1a;…

JAVA面试题分享一百九十九:RabbitMQ 发布确认高级

目录 一、前言 二、发布确认SpringBoot版本 介绍 实战 添加配置类 消息生产者 消息消费者 消息生产者发布消息后的回调接口 三、回退消息 介绍 四、实战 修改配置文件 修改回调接口 五、备份交换机 介绍 实战 修改高级确认发布 配置类 报警消费者 一、前言 …

基于单片机智能自动浇花系统设计

**单片机设计介绍&#xff0c;基于单片机智能自动浇花系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的智能自动浇花系统是一种可以自动感知周围环境&#xff0c;并执行相应动作的系统。通过使用传感器检测土…

【Netty】NIO与Netty核心概念

目录 NIO编程NIO介绍NIO和BIO的比较缓冲区(Buffer)基本介绍常用API缓冲区对象创建添加数据读取数据 通道(Channel)基本介绍Channel常用类ServerSocketChannelSocketChannel Selector (选择器)基本介绍常用API介绍示例代码 NIO 三大核心原理 Netty核心概念Netty 介绍原生 NIO 存…

【QT表格-6】QTableWidget的currentCellChanged实现中途撤销

背景&#xff1a; 【QT表格-1】QStandardItem的堆内存释放需要单独delete&#xff0c;还是随QStandardItemModel的remove或clear自动销毁&#xff1f;-CSDN博客 【QT表格-2】QTableWidget单元格结束编辑操作endEditting_qtablewidget 单元格编辑事件-CSDN博客 【QT表格-3】Q…

【Chrome】ERR_SSL_PROTOCOL_ERROR问题

文章目录 前言一、下载二、使用步骤总结 前言 Edge升级最新版后&#xff0c;有的https访问不了&#xff0c;报如下错误 发现新版Chrome以及Chromium内核访问nginx ssl时报错&#xff0c;顺着这个思路接着查看到大佬的结论&#xff1a;服务器nginx使用的openssl版本过低&#…

C++入门【12-C++ 数组】

C 数组 C 支持数组数据结构&#xff0c;它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据&#xff0c;但它往往被认为是一系列相同类型的变量。 数组的声明并不是声明一个个单独的变量&#xff0c;比如 number0、number1、...、number99&#xff0…

控制理论simulink+matlab

控制理论下的simulink和matlab使用 根轨迹LQR控制器简单使用状态观测器设计 根轨迹 z [-1]; %开环传递函数的零点 p [0 -2 -3 -4]; %开环传递函数的系统极点 k 1; %开环传递函数的系数&#xff0c;反映在比例上 g zpk(z,p,k); %生成开环传递函数%生成的传递函数如…

社交网络分析(汇总)

这里写自定义目录标题 写在最前面社交网络分析系列文章汇总目录 提纲问题一、社交网络相关定义和概念提纲问题1. 社交网络、社交网络分析&#xff1b;2. 六度分隔理论、贝肯数、顿巴数&#xff1b;3. 网络中的数学方法&#xff1a;马尔科夫过程和马尔科夫链、平均场理论、自组织…

使用JDBC对数据库进行简单操作

用Connection获得了数据库连接对象后&#xff0c;可以用Statement类型进行数据库操作。 在Statement对象中&#xff0c;有三种&#xff0c;分别是Statement&#xff0c;PrepareStatement&#xff0c;CallableStatement。 这三个的区别在于&#xff1a; Statement 用于执行不…

KubePi JWT 默认密钥权限绕过漏洞复现(CVE-2023-22463)

0x01 产品简介 KubePi 是一款简单易用的开源 Kubernetes 可视化管理面板。 0x02 漏洞概述 KubePi 存在权限绕过漏洞,攻击者可通过默认 JWT 密钥获取管理员权限控制整个平台,使用管理员权限操作核心的功能。 0x03 影响范围 KubePi <= 1.6.2 0x04 复现环境 FOFA: ti…

【Jenkins】远程API接口:Java 包装接口使用示例

jenkins-rest 库是一个面向对象的 Java 项目&#xff0c;它通过编程方式提供对 Jenkins REST API 的访问&#xff0c;以访问 Jenkins 提供的一些远程 API。它使用 jclouds 工具包构建&#xff0c;可以轻松扩展以支持更多 REST 端点。其功能集不断发展&#xff0c;用户可以通过拉…

怎么压缩过大的GIF图片?几个步骤轻松搞定!

GIF图片由于其图片格式&#xff0c;本身就会很大&#xff0c;但是微信QQ还有一些其他的社交平台对上传的表情包是有限制的&#xff0c;这个时候就需要借助一些图片处理工具对GIF进行压缩。 下面就向大家介绍三种好用的方法并展示具体的操作步骤。 一、使用嗨格式压缩大师进行压…

RouterSrv-路由功能

2023年全国网络系统管理赛项真题 模块B-Windows解析 题目 安装Remote Access服务开启路由转发,为当前实验环境提供路由功能。启用网络地址转换功能,实现内部客户端访问互联网资源。答题步骤 安装Remote Access服务开启路由转发,为当前实验环境提供路由功能。 配置网卡 加…

Day67力扣打卡

打卡记录 美丽塔 II&#xff08;前缀和 单调栈&#xff09; 链接 class Solution:def maximumSumOfHeights(self, maxHeights: List[int]) -> int:n len(maxHeights)stack collections.deque()pre, suf [0] * n, [0] * nfor i in range(n):while stack and maxHeights…

eNSP拓扑建立:RIP静态路由

实验名称&#xff1a; RIP动态路由协议 实验目的&#xff1a; 1、掌握RIPv1的配置方法 2、查看RIP路由分析过程 3、掌握测试RIP网络连通性的方法 步骤一:建立拓扑图 步骤二&#xff1a;配置PC终端的ip、子网掩码、网关。 步骤三&#xff1a;配置路由器&#xff0c;如图所示 步…