当前位置: 首页 > article >正文

Android使用声网SDK实现音视频互动(RTC)功能

一、前期准备

1、注册声网账号

声网官网

2、创建项目

拿到AppID,主要证书

二、代码部分

先上一下官方提供的demo地址:

Agora-RTC-QuickStart: 此仓库包含 Agora RTC Native SDK 的QuickStart示例项目。 - Gitee.comhttps://gitee.com/agoraio-community/Agora-RTC-QuickStart/tree/main/Android/Agora-RTC-QuickStart-Android可以在声网的帮助文档中看下图的教程很详细,或者无脑跑上面的demo,只需要填入声网控制台上获取到的appid,证书,和生成的临时token,以及生成临时token时填入的渠道号,但是控制台生成的临时token只有一天的有效期,下面会给出服务端生成临时token的代码,自己部署到服务器上,用客户端去调用接口

服务端:

提供一个获取token的接口

//还没要到代码,后续会补充上来,或者自行去帮助文档中查看,注意是rtc_token

客户端:

1、配置仓库

在settings.gradle中配置,主要是配置镜像

pluginManagement {repositories {maven { url "https://maven.aliyun.com/repository/public" }google()mavenCentral()gradlePluginPortal()}
}
dependencyResolutionManagement {repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories {maven { url "https://maven.aliyun.com/repository/public" }google()mavenCentral()}
}rootProject.name = "你的项目名称"
include ':app'

2、导入声网的sdk

在app模块下的build.gradle的dependencies中加入下面这行,注意下面这个是轻量级的库,详细的库在声网自行搜索

 implementation 'io.agora.rtc:lite-sdk:4.5.1' //替换为最新的

 3、添加防混淆规则

在app模块下的proguard-rules.pro文件中加入下面代码

-keep class io.agora.**{*;}
-dontwarn io.agora.**

4、 静态声明权限

在AndroidManifest.XML文件中声明如下权限

  <uses-featureandroid:name="android.hardware.camera"android:required="true" /><!--必要权限--><uses-permission android:name="android.permission.INTERNET"/><!--可选权限--><uses-permission android:name="android.permission.CAMERA"/><uses-permission android:name="android.permission.RECORD_AUDIO"/><uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/><uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/><uses-permission android:name="android.permission.BLUETOOTH"/><!-- 对于 Android 12.0 及以上且集成 v4.1.0 以下 SDK 的设备,还需要添加以下权限 --><uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/><!-- 对于 Android 12.0 及以上设备,还需要添加以下权限 --><uses-permission android:name="android.permission.READ_PHONE_STATE"/><uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>

5、具体代码部分

注:如果需要两个客户端互相传输音视频的话,直接用上面给的官方demo的代码就行,下面介绍的是对官方代码的一些封装,可以满足单西向传输,动态申请权限,调用临时token的接口,token本地存储及校验和不同页面的使用方法

(1)创建bean对象:
import java.io.Serializable/*
* name :相机的名称
* channelName :频道名称 必须唯一
* uid :用户唯一id
* token : 临时token
* lastPostTime :上次成功获取到数据的时间
*/
data class Camera(val name: String,val channelName: String = "",var uid:Int = 0,var token: String? = null,var lastPostTime: Long = 0
) : Serializable
(2)token的本地存储工具类
object SpUtil {private val context = App.app!!val sharedPreferences: SharedPreferences =context.getSharedPreferences("camera", Context.MODE_PRIVATE)//获取绑定的摄像头列表fun getCameraData(): List<Camera> {val listStr = sharedPreferences.getString("list", "")if (listStr == "") {return listOf<Camera>()} else {val typeToken = object : TypeToken<List<Camera>>() {}.typereturn Gson().fromJson(listStr, typeToken)}}//保存绑定的摄像头列表fun saveCameraListData(list: List<Camera>) {sharedPreferences.edit().apply {putString("list", Gson().toJson(list))apply()}}//更新绑定的摄像头列表fun updateCameraData(channelName: String, token: String, uid: Int) {val list = getCameraData().toMutableList()val localCameraList = list.filter { it.channelName == channelName }if (localCameraList.isNotEmpty()) {val localCamera = localCameraList.first()localCameraList.forEach {list.remove(it)}localCamera.token = tokenlocalCamera.lastPostTime = System.currentTimeMillis()localCamera.uid = uidlist.add(localCamera)}saveCameraListData(list)}//检查绑定摄像头的token是否过期fun checkToken(camera: Camera): Boolean {val list = getCameraData()val localCameraList = list.filter { it.channelName == camera.channelName }if (localCameraList.isNotEmpty()) {val localCamera = localCameraList.first()// 判断token是否过期val checkTime = System.currentTimeMillis() - localCamera.lastPostTime <  43200000if (localCamera.token != null && checkTime) {return true}}return false}//获取本地摄像头的数据fun getLocalCameraData(): Camera {val str = sharedPreferences.getString("localCamera", "")if (str == "") {val localCamera = Camera("本机", PlatformApp.getInstance().oaid)saveLocalCameraData(localCamera)return localCamera} else {val typeToken = object : TypeToken<Camera>() {}.typereturn Gson().fromJson(str, typeToken)}}//保存本地摄像头的数据fun saveLocalCameraData(camera: Camera) {sharedPreferences.edit().apply {putString("localCamera", Gson().toJson(camera))apply()}}//检查本地摄像头的token是否过期fun checkLocalCameraData(): Boolean {val localCamera = getLocalCameraData()// 判断token是否过期val checkTime = System.currentTimeMillis() - localCamera.lastPostTime < 43200000return localCamera.token != null && checkTime}
}
(3)RTC的管理类

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import android.view.SurfaceView
import android.widget.FrameLayout
import androidx.core.content.ContextCompat
import com.kwad.sdk.utils.bt.runOnUiThread
import fczs.colorscol.rrjj.base.App
import fczs.colorscol.rrjj.beans.Camera
import io.agora.rtc2.ChannelMediaOptions
import io.agora.rtc2.Constants
import io.agora.rtc2.IRtcEngineEventHandler
import io.agora.rtc2.RtcEngine
import io.agora.rtc2.RtcEngineConfig
import io.agora.rtc2.video.VideoCanvas
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.Call
import okhttp3.Callback
import okhttp3.FormBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.json.JSONObject
import java.io.IOException
import kotlin.coroutines.resumeclass RtcManger {private val baseContext = App.app!!// 填写项目的 App ID,可在声网控制台中生成private val appId = "你的AppId"//临时tokenvar token = ""//uid每个渠道应保持唯一性,为0的话,sdk会自动分配一个,但如果临时token是自己服务器生成的,那就应该保持和服务给的一致,否则token鉴权不过,无法加入渠道var uid = 0//要加入的渠道var channelName = "";private var mRtcEngine: RtcEngine? = null//权限回调码val PERMISSION_REQ_ID: Int = 22//远程视频视图容器var remoteVideoViewContainer: FrameLayout? = null//本地视图容器var localVideoViewContainer: FrameLayout? = nullprivate val mRtcEventHandler: IRtcEngineEventHandler = object : IRtcEngineEventHandler() {// 监听频道内的远端用户,获取用户的 uid 信息override fun onUserJoined(uid: Int, elapsed: Int) {runOnUiThread { // 获取 uid 后,设置远端视频视图setupRemoteVideo(uid)}}override fun onUserOffline(uid: Int, reason: Int) {super.onUserOffline(uid, reason)runOnUiThread {remoteVideoViewContainer?.removeAllViews()}}}fun init(camera: Camera) {this.channelName = camera.channelNamethis.token = camera.token ?: ""this.uid = camera.uid}/** clientRoleType: 用户角色类型,默认为 Constants.BROADCASTER (主播) 发送方 ,还可以是Constants.CLIENT_ROLE_AUDIENCE(观众) 接收方* localVideoViewContainer: 本地视频视图容器* remoteVideoViewContainer: 远端视频视图容器*/fun initializeAndJoinChannel(clientRoleType: Int = Constants.CLIENT_ROLE_BROADCASTER,localVideoViewContainer: FrameLayout? = null,remoteVideoViewContainer: FrameLayout? = null) {this.remoteVideoViewContainer = remoteVideoViewContainerthis.localVideoViewContainer = localVideoViewContainertry {// 创建 RtcEngineConfig 对象,并进行配置val config = RtcEngineConfig()config.mContext = baseContextconfig.mAppId = appId//添加远端视频视图handlerif (remoteVideoViewContainer != null) {config.mEventHandler = mRtcEventHandler}// 创建并初始化 RtcEnginemRtcEngine = RtcEngine.create(config)} catch (e: Exception) {throw RuntimeException("Check the error.")}// 启用视频模块mRtcEngine!!.enableVideo()//本地视图显示if (localVideoViewContainer != null) {// 开启本地预览mRtcEngine!!.startPreview()// 创建一个 SurfaceView 对象,并将其作为 FrameLayout 的子对象val container = localVideoViewContainerval surfaceView = SurfaceView(baseContext)container.addView(surfaceView)// 将 SurfaceView 对象传入声网实时互动 SDK,设置本地视图mRtcEngine!!.setupLocalVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0))}// 创建 ChannelMediaOptions 对象,并进行配置val options = ChannelMediaOptions()// 根据场景将用户角色设置为 BROADCASTER (主播) 或 AUDIENCE (观众)options.clientRoleType = clientRoleType// 直播场景下,设置频道场景为 BROADCASTING (直播场景)options.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING// 使用临时 Token 加入频道,自行指定用户 ID 并确保其在频道内的唯一性mRtcEngine!!.joinChannel(token, channelName, uid, options)}// 获取体验实时音视频互动所需的录音、摄像头等权限fun getRequiredPermissions(): Array<String> {// 判断 targetSDKVersion 31 及以上时所需的权限return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {arrayOf(Manifest.permission.RECORD_AUDIO,  // 录音权限Manifest.permission.CAMERA,  // 摄像头权限Manifest.permission.READ_PHONE_STATE,  // 读取电话状态权限Manifest.permission.BLUETOOTH_CONNECT, // 蓝牙连接权限)} else {arrayOf(Manifest.permission.RECORD_AUDIO,Manifest.permission.CAMERA)}}private fun setupRemoteVideo(uid: Int) {if (remoteVideoViewContainer != null) {val container = remoteVideoViewContainer!!val surfaceView = SurfaceView(baseContext)surfaceView.setZOrderMediaOverlay(true)container.addView(surfaceView)// 将 SurfaceView 对象传入声网实时互动 SDK,设置远端视图mRtcEngine!!.setupRemoteVideo(VideoCanvas(surfaceView,VideoCanvas.RENDER_MODE_FIT,uid))}}fun checkPermissions(context: Context): Boolean {for (permission in getRequiredPermissions()) {val permissionCheck = ContextCompat.checkSelfPermission(context, permission)if (permissionCheck != PackageManager.PERMISSION_GRANTED) {return false}}return true}fun close() {// 停止本地视频预览mRtcEngine?.stopPreview()// 离开频道mRtcEngine?.leaveChannel()localVideoViewContainer?.removeAllViews()}//获取后台数据  channelName:渠道名称  expire: 获取到的临时token的有效时间  localCamera: 是否是本地摄像头(摄像)suspend fun requestData(channelName: String, localCamera: Boolean = false): String {return suspendCancellableCoroutine { continuation ->val fromBody = FormBody.Builder().add("channelName",channelName).add("uid","能保持唯一性的字符串,如设备的oaid").build()val request = Request.Builder().url("自己服务器接口的url").post(fromBody).build()// 发起异步请求OkHttpClient().newCall(request).enqueue(object : Callback {override fun onResponse(call: Call, response: Response) {if (response.isSuccessful) {try {val responseBody = response.body?.string() ?: ""val jsonObject = JSONObject(responseBody)val code = jsonObject.getInt("code")if (code == 200) {var data = jsonObject.getJSONObject("data")val token = data.getString("token")val uid = data.getInt("uid")this@RtcManger.channelName = channelNamethis@RtcManger.token = tokenthis@RtcManger.uid = uidif (localCamera) {//更新本机摄像头发送视图时的数据SpUtil.saveLocalCameraData(Camera("本机", channelName, uid, token, System.currentTimeMillis()))} else {//更新本地存储的绑定摄像头的数据SpUtil.updateCameraData(channelName, token, uid)}continuation.resume("true")} else {continuation.resume("false")}} catch (e: Exception) {// JSON 解析失败continuation.resume("false")Log.e("Request failed", "Json解析失败:" + e.message.toString())}}}override fun onFailure(call: Call, e: IOException) {continuation.resume("false")Log.e("Request failed", "请求失败:" + e.message.toString())}})}}
}
(4)发送音视频界面
界面:

确报有一个下面的布局就行

  <FrameLayoutandroid:id="@+id/local_video_view_container"android:layout_width="match_parent"android:layout_height="match_parent"/>
代码:

在activity/fragment中声明如下变量和方法,示例中发送端是fragment,后面接收端是activity的示例代码

private val rtcManger = RtcManger()
private val localCamera by lazy { SpUtil.getLocalCameraData() }//正常动态申请权限应该在activity的结果回调方法中写,但如果在onResume中执行判断就可以不用写结果回调方法
override fun onResume() {super.onResume()// 如果已经授权,则初始化 RtcEngine 并加入频道if (rtcManger.checkPermissions(requireContext())) {binding.permissionLL.container.visibility = View.GONEcheckToken()} else {//显示无权限布局binding.permissionLL.apply {container.visibility = View.VISIBLEpermissionBt.setOnClickListener {ActivityCompat.requestPermissions(requireActivity(),rtcManger.getRequiredPermissions(),rtcManger.PERMISSION_REQ_ID)}}}}private fun checkToken() {binding.permissionLL.container.visibility = View.GONEif (SpUtil.checkLocalCameraData()) {rtcManger.init(localCamera)start()} else {CoroutineScope(Dispatchers.Main).launch {connectDialog.show()val result = withContext(Dispatchers.IO) {rtcManger.requestData(localCamera.channelName, true)}.toBoolean()connectDialog.dismiss()if (result) {start()} else {Toast.makeText(requireContext(), "服务器异常,请稍后重试", Toast.LENGTH_SHORT).show()}}}}private fun start() {rtcManger.initializeAndJoinChannel(Constants.CLIENT_ROLE_BROADCASTER,binding.localVideoViewContainer)}private fun stop() {rtcManger.close()}override fun onPause() {super.onPause()stop()}
 (5)接收音视频界面
界面:
    <FrameLayoutandroid:id="@+id/remote_video_view_container"android:layout_width="match_parent"android:layout_height="match_parent" />
代码: 

和上面发送的大差不差,只是进入acticity时传递了要发送端的Camera对象,里面有渠道号,token等信息,这些发送端在拉token的时候已经通过SpUtil存到本地了,自己读取一下需要的

private lateinit var camera: Camerapublic override fun onCreate(savedInstanceState: Bundle?) {camera = intent.getSerializableExtra("camera") as Camera}override fun onResume() {super.onResume()// 如果已经授权,则初始化 RtcEngine 并加入频道if (rtcManger.checkPermissions(this)) {binding.permissionLL.container.visibility = View.GONEcheckToken()} else {binding.permissionLL.apply {container.visibility = View.VISIBLEpermissionBt.setOnClickListener {ActivityCompat.requestPermissions(this@CameraActivity,rtcManger.getRequiredPermissions(),rtcManger.PERMISSION_REQ_ID)}}}
}@Deprecated("Deprecated in Java")override fun onRequestPermissionsResult(requestCode: Int,permissions: Array<String?>,grantResults: IntArray) {super.onRequestPermissionsResult(requestCode, permissions, grantResults)// 系统权限申请回调if (rtcManger.checkPermissions(this)) {checkToken()}}private fun checkToken() {binding.permissionLL.container.visibility = View.GONEif (SpUtil.checkToken(camera)) {rtcManger.init(camera)start()} else {CoroutineScope(Dispatchers.Main).launch {connectDialog.show()val result = withContext(Dispatchers.IO) {rtcManger.requestData(camera.channelName)}.toBoolean()if (result) {connectDialog.dismiss()start()} else {connectDialog.showFailView {finish()}}}}}fun start() {rtcManger.initializeAndJoinChannel(Constants.CLIENT_ROLE_AUDIENCE,null,binding.remoteVideoViewContainer)}override fun onPause() {super.onPause()binding.remoteVideoViewContainer.removeAllViews() rtcManger.close()}

ok,就是这样,总体来说是很简单的,发送端和接收端只是start方法不一样,其余的都差不多,希望上面的经验能帮到你

相关文章:

Android使用声网SDK实现音视频互动(RTC)功能

一、前期准备 1、注册声网账号 声网官网 2、创建项目 拿到AppID&#xff0c;主要证书 二、代码部分 先上一下官方提供的demo地址&#xff1a; Agora-RTC-QuickStart: 此仓库包含 Agora RTC Native SDK 的QuickStart示例项目。 - Gitee.comhttps://gitee.com/agoraio-comm…...

FPGA_modelsim错误总结

1&#xff0c; 使用modelsim仿真DDR3报错Module ‘SIP_PHY_CONTROL‘ is not defined 在配置ddr3的时候vivado 速度太慢了&#xff0c;所以选用modelsim。我的是2018.3vivado&#xff0c;modelsim用了10.4 但是不行报错 然后看了帖子说 questasim可以下载了还是报错。 然后又…...

CExercise_09_2动态拼接字符串_1字符串拼接

题目&#xff1a; 标准库函数strcat会将一个字符串追加到另一个字符串的末尾。 现在我们编写一个函数把两个字符串拼接起来&#xff0c;返回拼接的结果&#xff0c;但要求不改变其中任何一个字符串。其函数声明如下&#xff1a; char* my_strcat(const char* prefix, const cha…...

【愚公系列】《高效使用DeepSeek》063-海关数据获取和管理

🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! 👉 江湖人称"愚公搬代码",用七年如一日的精神深耕技术领域,以"…...

了解 DeFi:去中心化金融的入门指南与未来展望

去中心化金融&#xff0c;或 DeFi&#xff0c;代表着全球金融体系运作方式的革命性转变。它是一个总称&#xff0c;指的是一个不断增长的去中心化应用程序&#xff08;dapp&#xff09;、协议和平台生态系统&#xff0c;这些生态系统构建在公共区块链网络上&#xff0c;无需传统…...

Python爬虫第10节-lxml解析库用 XPath 解析网页

目录 引言 一、XPath简介 二、XPath常用规则 三、实例讲解 四、节点的选取 4.1 所有节点的选取 4.2 子节点的选取 4.3 父节点选取 五、属性匹配获取及文本获取 5.1 属性匹配 5.2 文本获取 5.3 属性获取 5.4 属性多值匹配 5.5 多属性匹配 六、按序选择 七、节点…...

Python基础知识点(类和对象)

""" 编程思维---解决问题的方式方法 面向过程---C语言 面向对象---C java python python中封装类的语法 class 类名&#xff08;父类&#xff09; 类体 注意&#xff1a; 1.类名--约定 大驼峰法 首字母要大写 2.父类如果有的话就写&#xff0c;没有的话…...

【LeetCode 热题100】139:单词拆分(动态规划全解析+细节陷阱)(Go语言版)

&#x1f680; LeetCode 热题 139&#xff1a;单词拆分&#xff08;Word Break&#xff09;| 动态规划全解析细节陷阱 &#x1f4cc; 题目描述 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请判断 s 是否可以由字典中出现的单词拼接成。 说明&#xff1a;不要求字典…...

【C语言】预处理(预编译)(C语言完结篇)

一、预定义符号 前面我们学习了C语言的编译和链接。 在C语言中设置了一些预定义符号&#xff0c;其可以直接使用&#xff0c;预定义符号也是在预处理期间处理的。 如下&#xff1a; 可以看到上面的预定义符号&#xff0c;其都有两个短下划线&#xff0c;要注意的是&#xff…...

关于聊天室数据库建表

首先了解一下外键 ​​一、外键的本质​​ ​​定义​​&#xff1a;外键是某个表中的字段&#xff08;或字段组合&#xff09;&#xff0c;其值必须与另一张表的主键值相匹配。 ​​核心作用​​&#xff1a;强制数据一致性&#xff0c;维护表间关系。 二、外键的核心用途…...

Java 面试总结

1. Java 并发volatile 问题代码 class NumberDemo { //private AtomicInteger count = new AtomicInteger(0);private volatile int count = 0;public void add() {this.count++;}public int getCount() {return this.count;} }public class ThreadDemo {public static void m…...

基于 OpenHarmony 5.0 的星闪轻量型设备应用开发-Ch1 开发环境搭建

写在前面&#xff1a; 文本所写的工程创建均是基于 HH-SPARK-WS63 星闪无线模组。 此篇是系列文章《基于 OpenHarmony5.0 的星闪轻量型设备应用开发》的第 1 章。 1.1 介绍 HH-SPARK-WS63 星闪无线模组&#xff08;以下简称 WS63&#xff09;是由润和软件推出的基于海思 WS63V…...

离线安装 nvidia-docker2(nvidia-container-toolkit)

很多时候大家都有用docker使用gpu的需求&#xff0c;但是因为网络等原因不是那么好用&#xff0c;这里留了一个给ubuntu的安装包&#xff0c;网络好的话也提供了在线安装方式 安装 nvidia-docker2 1 离线安装 &#xff08;推荐&#xff09; unzip解压后进入目录 dpkg -i *.d…...

H.264 NVMPI解码性能优化策略

H.264 NVMPI解码性能优化策略‌ ‌1. 硬件与驱动配置‌ ‌JetPack版本匹配‌&#xff1a;确保NVIDIA Jetson设备的JetPack SDK版本与CUDA驱动兼容&#xff0c;避免因驱动不匹配导致硬件解码性能下降‌8。‌显存分配优化‌&#xff1a;调整FFmpeg的-hwaccel_device参数指定GPU…...

2025年道路运输安全员证考试主要内容

道路运输安全员考试主要针对从事道路运输企业安全生产管理的人员&#xff0c;考核其对道路运输安全法律法规、安全管理知识及应急处置能力的掌握。 考试内容 1. 理论知识部分 安全生产法律法规 国家安全生产方针政策&#xff08;如“安全第一、预防为主、综合治理”&#x…...

10、nRF52xx蓝牙学习(GPIOTE事件模式中断组件)

由于驱动组件库是可以直接调用的&#xff0c;那么编程者的任务就只有编写主函数 main。 #include <stdbool.h> #include "nrf.h" #include "nrf_drv_gpiote.h" #include "app_error.h" #include "boards.h" /* #ifdef BSP_BUTTO…...

第7篇:Linux程序访问控制FPGA端LEDR<五>

Q&#xff1a;如何设计.c程序代码实现FPGA端外设LEDR流水灯&#xff1f; A&#xff1a;在DE1-SoC开发板上实现的流水灯效果&#xff1a;一次只点亮一个红色LED&#xff0c;初始状态为向左移动直至点亮LEDR9&#xff0c;然后改变移动的方向为向右直至点亮LEDR0&#xff0c;以此…...

类名与协议名相同,开发中应该避免吗?

在 Objective-C 开发中&#xff0c;协议与实现类之间的命名关系非常重要。虽然语言允许协议名和类名相同&#xff0c;但从可读性和维护性等角度出发&#xff0c;这种做法并不推荐。本文通过一个典型示例展开分析&#xff0c;并提供更合理的命名建议。 一、示例 在某项目中&…...

linux下io操作详细解析

在 Linux 系统下&#xff0c;IO&#xff08;输入/输出&#xff09;操作是程序与外部设备&#xff08;如文件、网络等&#xff09;交互的重要方式。Linux 提供了丰富的系统调用和库函数来支持各种 IO 操作。以下是对 Linux 下 IO 操作的详细解析&#xff0c;包括文件 IO、网络 I…...

Unity 实现伤害跳字

核心组件&#xff1a; Dotween TextMeshPro 过程轨迹如下图&#xff1a; 代码如下&#xff1a; using System.Collections; using System.Collections.Generic; using DG.Tweening; using TMPro; using UnityEngine; using UnityEngine.Pool;public class …...

Java集合框架:核心接口与关系全解析

精心整理了最新的面试资料和简历模板&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 一、集合框架概述 Java集合框架&#xff08;Java Collections Framework, JCF&#xff09;是Java中用于存储、操作和管理数据集合的核心工具库。它提供了一套…...

008二分答案+贪心判断——算法备赛

二分答案贪心判断 有些问题&#xff0c;从已知信息推出答案&#xff0c;细节太多&#xff0c;过程繁杂&#xff0c;不易解答。 从猜答案出发&#xff0c;贪心地判断该答案是否合法是个不错的思路&#xff0c;这要求所有可能的答案是单调的&#xff08;例&#xff1a;x满足条件…...

计算机视觉与深度学习 | 视觉SLAM学习思路总结与视觉SLAM发展历程(1986年至2025年)

视觉SLAM(Simultaneous Localization and Mapping,同时定位与建图)是计算机视觉和机器人领域的重要研究方向,涉及数学、几何、优化、传感器融合等多学科知识。以下是学习视觉SLAM的系统化思路总结,适合从入门到进阶的学习路径:视觉SLAM学习思路总结 一、基础准备 数学基…...

衣橱管理助手系统(衣服推荐系统)(springboot+ssm+vue+mysql)含运行文档

衣橱管理助手系统(衣服推荐系统)(springbootssmvuemysql)含运行文档 该系统名为衣橱管理助手&#xff0c;是一个衣物搭配管理系统&#xff0c;主要功能包括衣物档案管理、衣物搭配推荐、搭配收藏以及套装智能推荐。用户可以通过系统进行衣物的搭配和收藏管理&#xff0c;系统提…...

学习笔记四——Rust 函数通俗入门

&#x1f980; Rust 函数通俗入门 &#x1f4d8; Rust 是一门语法精炼但设计严谨的系统级语言。本文围绕函数这一主线&#xff0c;带你真正搞懂 Rust 最关键的语法思想&#xff0c;包括表达式驱动、闭包捕获、Trait 限制、生命周期标注与所有权规则&#xff0c;每遇到一个新概念…...

【场景应用3】audio_classification:音频分类的微调

1 引言 本笔记展示了如何对多语种预训练的语音模型进行微调,以实现自动语音识别(Automatic Speech Recognition)。 本笔记旨在使用SUPERB数据集中的关键词检测子集,并且可以使用任何来自模型库(Model Hub)的语音模型检查点,只要该模型有一个包含序列分类头(Sequence …...

文件上传做题记录

1&#xff0c;[SWPUCTF 2021 新生赛]easyupload2.0 直接上传php 再试一下phtml 用蚁剑连发现连不上 那就只要命令执行了 2&#xff0c;[SWPUCTF 2021 新生赛]easyupload1.0 当然&#xff0c;直接上传一个php是不行的 phtml也不行&#xff0c;看下是不是前端验证&#xff0c;…...

【Pandas】pandas DataFrame to_numpy

Pandas2.2 DataFrame Conversion 方法描述DataFrame.astype(dtype[, copy, errors])用于将 DataFrame 中的数据转换为指定的数据类型DataFrame.convert_dtypes([infer_objects, …])用于将 DataFrame 中的数据类型转换为更合适的类型DataFrame.infer_objects([copy])用于尝试…...

Vue环境搭建:vue+idea

目录 第一章、Vue环境搭建&#xff1a;安装node2.1&#xff09;node的下载2.2&#xff09;配置node的环境变量2.3&#xff09;常见的npm命令 第二章、使用idea创建vue工程2.1&#xff09;在IDEA中设置国内镜像2.2&#xff09;在IDEA中进行脚手架安装2.3&#xff09;在IDEA中创建…...

ECMAScript 7~10 新特性

ECMAScript 7 新特性 ECMAScript 6 新特性&#xff08;一&#xff09; ECMAScript 6 新特性&#xff08;二&#xff09; ECMAScript 7~10 新特性&#xff08;本文&#xff09; 1. 数组方法 Array.prototype.includes() 用来检测数组中是否包含指定元素&#xff0c;返回布尔值&…...