Android 使用Camera1实现相机预览、拍照、录像
1. 前言
本文介绍如何从零开始,在Android中实现Camera1的接入,并在文末提供Camera1Manager工具类,可以用于快速接入Camera1。
Android Camera1 API虽然已经被Google废弃,但有些场景下不得不使用。
并且Camera1返回的帧数据是NV21,不像Camera2、CameraX那样,需要自己再转一层,才能得到NV21。
Camera1的API调用也比Camera2简单不少,和CameraX的简单程度差不多,所以在一定的场景下,Camera1还是有其用途的。
2. 前置操作
2.1 添加权限
在AndroidManifest中添加如下权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
2.2 申请权限
别忘了申请权限
ActivityCompat.requestPermissions(this@WelComeActivity,arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE,android.Manifest.permission.RECORD_AUDIO,android.Manifest.permission.CAMERA),123
)
2.3 声明XML布局
新建一个Activity,在其XML中声明SurfaceView
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:background="@color/black"android:layout_width="match_parent"android:layout_height="match_parent"><SurfaceViewandroid:id="@+id/surfaceView"android:layout_width="0dp"android:layout_height="0dp"app:layout_constraintDimensionRatio="9:16"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>
3. 实现预览功能
3.1 添加SurfaceView的回调
binding.surfaceView.holder.addCallback(surfaceCallback)private var surfaceCallback: SurfaceHolder.Callback = object : SurfaceHolder.Callback {// Surface创建时override fun surfaceCreated(holder: SurfaceHolder) {}// Surface改变时override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}// Surface销毁时override fun surfaceDestroyed(holder: SurfaceHolder) {}
}
3.2 打开相机
当Surface创建时,也就是在surfaceCreated的时候,打开相机
private var camera: Camera? = null
private fun openCamera(holder: SurfaceHolder) {try {camera = Camera.open(cameraId)} catch (e: Exception) {e.printStackTrace()}
}
3.3 开始预览
当我们打开相机后,就可以开始预览了
这里首先将设置camera1预览的尺寸,一般来说,通过camera!!.parameters.supportedPreviewSizes获取到的列表中,第一项就是最推荐的尺寸了。
private fun setPreviewSize() {//获取摄像头支持的宽、高val supportedPreviewSizes: List<Camera.Size> = camera!!.parameters.supportedPreviewSizessupportedPreviewSizes.forEach {Log.i("ZZZZ", "${it.width}*${it.height}")}val parameters = camera?.parametersval size = supportedPreviewSizes[0]parameters?.setPreviewSize(size.width, size.height)camera?.setParameters(parameters)
}
接着,将SurfaceHolder设置到camera中。setPreviewDisplay接受一个SurfaceHolder对象作为参数,该对象表示预览显示的表面。通过调用setPreviewDisplay方法,可以将相机的预览数据输出到指定的表面对象上,从而在预览界面中显示出相机的拍摄画面。
camera?.setPreviewDisplay(holder)
接着调用setDisplayOrientation方法来设置相机的预览方向。该方法接受一个参数,即预览方向的度数。例如,如果要在竖直模式下使用相机,而默认的预览方向是水平的,那么就可以通过调用setDisplayOrientation方法将预览方向顺时针旋转90度。
camera?.setDisplayOrientation(90)
最后,调用startPreview()就可以启动相机的预览了
camera?.startPreview()
来看一下完整代码
private fun startPreview(holder: SurfaceHolder) {try {setPreviewSize()camera?.setPreviewDisplay(holder)camera?.setDisplayOrientation(90)camera?.startPreview()} catch (e: IOException) {e.printStackTrace()}
}
3.4 效果如下
4. 实现拍照功能
4.1 调用拍照接口
要进行拍照,调用camera.takePicture即可,它共有3个回调参数
ShutterCallback shutter(捕获图片瞬间的回调):快门回调是在拍照时快门按下的瞬间调用的回调。它允许您在拍照时执行一些自定义操作,例如触发闪光灯或显示自定义的拍照界面。PictureCallback raw(原始图像数据回调):原始图像数据回调是在拍照后,获取到原始未压缩的数据时调用的回调。您可以在这个回调中对图像数据进行处理或保存。PictureCallback jpeg(JPEG图像数据回调):JPEG图像数据回调是在拍照后,获取到图像的JPEG格式数据时调用的回调。您可以在这个回调中对JPEG图像数据进行处理或保存。
这里我们只需要用到jpeg回调
private val threadPool = Executors.newCachedThreadPool()binding.btnTakePicture.setOnClickListener {camera?.takePicture(null,null,{ data, camera ->//jpeg回调})
}
4.2 在jpeg回调中保存图片
//MediaFileUtils类详见本文附录
val pictureFile: File = MediaFileUtils.getOutputMediaFile(MEDIA_TYPE_IMAGE)!!
try {val fos = FileOutputStream(pictureFile)fos.write(data)fos.close()
} catch (e: FileNotFoundException) {Log.d(TAG, "File not found: ${e.message}")errorCallBack.invoke(e)
} catch (e: IOException) {Log.d(TAG, "Error accessing file: ${e.message}")errorCallBack.invoke(e)
}
来查看下效果,可以看到图片已经被保存了,但是图片的方向目前是有问题的。

4.3 解决图片保存的方向问题
所以,我们需要先将图片转成bitmap,旋转角度后,再保存
修改代码为如下代码
//路径示例 : /storage/emulated/0/Pictures/MyCameraApp/IMG_20230726_135652.jpg
val pictureFile: File = MediaFileUtils.getOutputMediaFile(MediaFileUtils.MEDIA_TYPE_IMAGE)!!
val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size)
val matrix = Matrix()
matrix.postRotate(270F)
val rotatedBitmap: Bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true
)
//ImageUtils需要依赖 implementation 'com.blankj:utilcodex:1.31.1'
ImageUtils.save(rotatedBitmap, pictureFile, Bitmap.CompressFormat.JPEG)
来看一下效果,可以看到现在图片方向是对了,但是图片左右的内容是相反的

4.4 解决图片保存镜像问题
要解决图片的镜像问题,就调用一下matrix.postScale左右水平变换就好了
matrix.postScale(-1F, 1F, bitmap.width / 2F, bitmap.height / 2F)
完整代码如下
val pictureFile: File =
MediaFileUtils.getOutputMediaFile(MediaFileUtils.MEDIA_TYPE_IMAGE)!!
//路径示例 : /storage/emulated/0/Pictures/MyCameraApp/IMG_20230726_135652.jpg
val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size)
val matrix = Matrix()
matrix.postRotate(270F)
matrix.postScale(-1F, 1F, bitmap.width / 2F, bitmap.height / 2F)
val rotatedBitmap: Bitmap = Bitmap.createBitmap(
bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true
)
//ImageUtils需要依赖 implementation 'com.blankj:utilcodex:1.31.1'
ImageUtils.save(rotatedBitmap, pictureFile, Bitmap.CompressFormat.JPEG)
5. 实现录像功能
要录制视频,需要使用MediaRecorder,若要使用 Camera1 拍摄视频,需要谨慎管理 Camera 和 MediaRecorder,并且必须按特定顺序调用相应方法。您必须遵循以下顺序,才能使您的应用正常工作:
- 打开相机。
- 做好准备,并开始预览(如果您的应用会显示正在录制的视频,而通常情况下都是如此)。
- 通过调用
Camera.unlock()解锁相机,以供 MediaRecorder 使用。 - 通过在
MediaRecorder上调用以下方法来配置录制:- 通过
setCamera(camera)关联您的Camera实例。 - 调用
setAudioSource(MediaRecorder.AudioSource.CAMCORDER)。 - 调用
setVideoSource(MediaRecorder.VideoSource.CAMERA)。 - 调用
setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P))以设置质量。 - 调用
setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())。 - 如果您的应用提供视频预览,请调用
setPreviewDisplay(preview?.holder?.surface)。 - 调用
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)。 - 调用
setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)。 - 调用
setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)。 - 调用
prepare()以完成MediaRecorder配置。
- 通过
- 如需开始录制,请调用
MediaRecorder.start()。 - 如需停止录制,请按以下顺序调用以下方法:
- 调用
MediaRecorder.stop()。 - (可选)通过调用
MediaRecorder.reset()移除当前的MediaRecorder配置。 - 调用
MediaRecorder.release()。 - 通过调用
Camera.lock()锁定相机,以便将来的MediaRecorder会话可以使用它。
- 调用
- 如需停止预览,请调用
Camera.stopPreview()。 - 最后,如需释放
Camera以供其他进程使用,请调用Camera.release()。
具体可以见 Camera1 录制视频
下面直接附上代码,直接如下代码就好了
5.1 开始录制
fun startVideo(holder: SurfaceHolder) {mediaRecorder = MediaRecorder()//解锁相机,以供 MediaRecorder 使用camera?.unlock()//设置要用于视频捕获的相机mediaRecorder.setCamera(camera)//设置音频源mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER)//设置视频源mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA)//设置视频的输出格式和编码mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P))//设置输出视频播放的方向mediaRecorder.setOrientationHint(270)//设置输出文件mediaRecorder.setOutputFile(getVideoFilePath(this))//指定 SurfaceView 预览布局元素mediaRecorder.setPreviewDisplay(holder.surface)try {mediaRecorder.prepare()} catch (e: IOException) {e.printStackTrace()releaseMediaRecorder()}Handler().postDelayed({try {mediaRecorder.start()} catch (e: IOException) {e.printStackTrace()releaseMediaRecorder()}}, 10)
}fun getVideoFilePath(context: Context?): String {val filename = "VIDEO_${System.currentTimeMillis()}.mp4"val dir = context?.getExternalFilesDir("video")return "${dir!!.path}/$filename"
}
5.2 停止播放
fun stopVideo() {mediaRecorder.stop()mediaRecorder.release()camera?.lock()
}
5.3 释放资源
fun releaseMediaRecorder() {if (mediaRecorder != null) {mediaRecorder.reset() // 清除配置mediaRecorder.release()//mediaRecorder = nullcamera?.lock()}
}
6. CameraHelper工具类
可以直接使用这个工具类,来快速接入Camera1
class CameraHelper(private val activity: AppCompatActivity,private var cameraId: Int,private var width: Int = 720,private var height: Int = 1280,
) : Camera.PreviewCallback {private var surfaceHolder: SurfaceHolder? = nullprivate var surfaceTexture: SurfaceTexture? = nullprivate var mCamera: Camera? = nullprivate var buffer: ByteArray? = nullprivate var bytes: ByteArray? = null/*** 打开相机** @param cameraId 后摄 Camera.CameraInfo.CAMERA_FACING_BACK* 前摄 Camera.CameraInfo.CAMERA_FACING_FRONT*/private fun open(cameraId: Int) {//获得camera对象mCamera = Camera.open(cameraId)mCamera?.let { camera ->//配置camera的属性val parameters = camera.parameters//设置预览数据格式为nv21parameters.previewFormat = ImageFormat.NV21//这是摄像头宽、高setPreviewSize(parameters!!)// 设置摄像头 图像传感器的角度、方向setPreviewOrientation(cameraId)camera.parameters = parameters}}/*** 切换摄像头*/fun switchCamera() {val cameraId = if (cameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {Camera.CameraInfo.CAMERA_FACING_FRONT} else {Camera.CameraInfo.CAMERA_FACING_BACK}switchCamera(cameraId)}/*** 切换摄像头* @param cameraId 指定摄像头ID*/fun switchCamera(cameraId: Int) {this.cameraId = cameraIdpreviewAlign()}private fun previewAlign() {stopPreview()if (surfaceHolder != null) {startPreview(surfaceHolder!!)} else {startPreview(surfaceTexture!!)}}/*** 停止预览*/fun stopPreview() {if (mCamera != null) {mCamera?.setPreviewCallback(null)mCamera?.stopPreview()mCamera?.release()mCamera = null}}/*** 开始预览*/fun startPreview(surfaceHolder: SurfaceHolder) {open(cameraId)this.surfaceHolder = surfaceHolderbuffer = ByteArray(width * height * 3 / 2)bytes = ByteArray(buffer!!.size)//数据缓存区mCamera?.addCallbackBuffer(buffer)mCamera?.setPreviewCallbackWithBuffer(this)//设置预览画面mCamera?.setPreviewDisplay(surfaceHolder)mCamera?.startPreview()}fun startPreview(surfaceTexture: SurfaceTexture) {open(cameraId)this.surfaceTexture = surfaceTexturebuffer = ByteArray(width * height * 3 / 2)bytes = ByteArray(buffer!!.size)//数据缓存区mCamera?.addCallbackBuffer(buffer)mCamera?.setPreviewCallbackWithBuffer(this)//设置预览画面mCamera?.setPreviewTexture(surfaceTexture)mCamera?.startPreview()}private val threadPool = Executors.newCachedThreadPool()/*** 拍摄照片*/fun takePicture(completedCallBack: () -> Unit, errorCallBack: (Exception) -> Unit) {mCamera?.takePicture(null, null, object : Camera.PictureCallback {override fun onPictureTaken(data: ByteArray?, camera: Camera?) {previewAlign()threadPool.execute {val pictureFile: File = getOutputMediaFile(MEDIA_TYPE_IMAGE)!!//路径示例 : /storage/emulated/0/Pictures/MyCameraApp/IMG_20230726_135652.jpgval bitmap = BitmapFactory.decodeByteArray(data, 0, data!!.size)val matrix = Matrix()//修正图片方向,这里只是示例,需要根据实际手机方位来决定图片角度matrix.postRotate(if (cameraId==1) 270F else 90F)if (cameraId==1) {//postScale在矩阵变换之后进行缩放matrix.postScale(-1F, 1F, bitmap.width / 2F, bitmap.height / 2F)}val rotatedBitmap: Bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)ImageUtils.save(rotatedBitmap, pictureFile, Bitmap.CompressFormat.JPEG)completedCallBack.invoke()}}})}override fun onPreviewFrame(data: ByteArray, camera: Camera?) {camera!!.addCallbackBuffer(data)}private fun setPreviewSize(parameters: Camera.Parameters) {//获取摄像头支持的宽、高val supportedPreviewSizes = parameters.supportedPreviewSizesvar size = supportedPreviewSizes[0]Log.d(TAG, "Camera支持: " + size.width + "x" + size.height)//选择一个与设置的差距最小的支持分辨率var m: Int = Math.abs(size.height * size.width - width * height)supportedPreviewSizes.removeAt(0)val iterator: Iterator<Camera.Size> = supportedPreviewSizes.iterator()//遍历while (iterator.hasNext()) {val next = iterator.next()Log.d(TAG, "支持 " + next.width + "x" + next.height)val n: Int = Math.abs(next.height * next.width - width * height)if (n < m) {m = nsize = next}}width = size.widthheight = size.heightparameters.setPreviewSize(width, height)Log.d(TAG, "预览分辨率 width:" + size.width + " height:" + size.height)}private val mOnChangedSizeListener: OnChangedSizeListener? = nullprivate fun setPreviewOrientation(cameraId: Int) {val info = CameraInfo()Camera.getCameraInfo(cameraId, info)val rotation = activity.windowManager.defaultDisplay.rotationvar degrees = 0when (rotation) {Surface.ROTATION_0 -> {degrees = 0mOnChangedSizeListener?.onChanged(height, width)}Surface.ROTATION_90 -> {degrees = 90mOnChangedSizeListener?.onChanged(width, height)}Surface.ROTATION_180 -> {degrees = 180mOnChangedSizeListener?.onChanged(height, width)}Surface.ROTATION_270 -> {degrees = 270mOnChangedSizeListener?.onChanged(width, height)}}var result: Intif (info.facing == CameraInfo.CAMERA_FACING_FRONT) {result = (info.orientation + degrees) % 360result = (360 - result) % 360 // compensate the mirror} else { // back-facingresult = (info.orientation - degrees + 360) % 360}//设置角度, 参考源码注释mCamera!!.setDisplayOrientation(result)}private lateinit var mediaRecorder: MediaRecorderprivate val handle = Handler(Looper.getMainLooper())/*** 开始录像*/fun startVideo(path: String) {mediaRecorder = MediaRecorder()//解锁相机,以供 MediaRecorder 使用mCamera?.unlock()//设置要用于视频捕获的相机mediaRecorder.setCamera(mCamera)//设置音频源mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER)//设置视频源mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA)//设置视频的输出格式和编码mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH))//设置输出视频播放的方向,这里只是示例,需要根据实际手机方位来决定角度mediaRecorder.setOrientationHint(if (cameraId == 1) 270 else 90)//设置输出文件mediaRecorder.setOutputFile(path)//指定 SurfaceView 预览布局元素mediaRecorder.setPreviewDisplay(surfaceHolder!!.surface)try {mediaRecorder.prepare()} catch (e: IOException) {e.printStackTrace()releaseMediaRecorder()}handle.postDelayed({try {mediaRecorder.start()} catch (e: IOException) {e.printStackTrace()releaseMediaRecorder()}}, 10)}/*** 释放资源*/fun releaseMediaRecorder() {if (mediaRecorder != null) {mediaRecorder.reset() // 清除配置mediaRecorder.release()//mediaRecorder = nullmCamera?.lock()}}/*** 停止录像*/fun stopVideo() {mediaRecorder.stop()mediaRecorder.release()mCamera?.lock()}interface OnChangedSizeListener {fun onChanged(width: Int, height: Int)}companion object {private const val TAG = "CAMERA_HELPER"}
}
7. 附录
7.1 MediaFileUtils
获取媒体文件路径的工具类
object MediaFileUtils {val MEDIA_TYPE_IMAGE = 1val MEDIA_TYPE_VIDEO = 2/** Create a file Uri for saving an image or video */fun getOutputMediaFileUri(type: Int): Uri {return Uri.fromFile(getOutputMediaFile(type))}/** Create a File for saving an image or video */fun getOutputMediaFile(type: Int): File? {// To be safe, you should check that the SDCard is mounted// using Environment.getExternalStorageState() before doing this.val mediaStorageDir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),"MyCameraApp")// This location works best if you want the created images to be shared// between applications and persist after your app has been uninstalled.// Create the storage directory if it does not existmediaStorageDir.apply {if (!exists()) {if (!mkdirs()) {Log.d("MyCameraApp", "failed to create directory")return null}}}// Create a media file nameval timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())return when (type) {MEDIA_TYPE_IMAGE -> {File("${mediaStorageDir.path}${File.separator}IMG_$timeStamp.jpg")}MEDIA_TYPE_VIDEO -> {File("${mediaStorageDir.path}${File.separator}VID_$timeStamp.mp4")}else -> null}}
}
7.2. 本文源码下载
Android Camera1 Demo - 实现预览、拍照、录制视频功能
相关文章:
Android 使用Camera1实现相机预览、拍照、录像
1. 前言 本文介绍如何从零开始,在Android中实现Camera1的接入,并在文末提供Camera1Manager工具类,可以用于快速接入Camera1。 Android Camera1 API虽然已经被Google废弃,但有些场景下不得不使用。 并且Camera1返回的帧数据是NV21…...
2024字节跳动校招面试真题汇总及其解答(四)
12.Java的类加载机制 Java的类加载机制是指将描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。 类的加载过程分为以下五个阶段: 加载:将Class文件从磁盘读入内存,并…...
网页的快捷方式打开自动全屏--Chrome、Firefox 浏览器相关设置
Firefox 的全屏方式与 Chrome 不同,Chrome 自带全屏模式以及APP模式,通过简单的参数即可设置,而Firefox暂时么有这个功能,Firefox 的全屏功能可以通过全屏插件实现。 全屏模式下,按 F11 不会退出全屏,鼠标…...
LabVIEW使用ModbusTCP协议构建分布式测量系统
LabVIEW使用ModbusTCP协议构建分布式测量系统 分布式测量系统主要用于监控远程物体。这种系统允许对系统用户获得的数据进行全面的数据收集、处理、存储和组织访问。它们可能包括许多不同类型的传感器。 在任何具有互联网接入的个人计算机上运行的软件都会发送来自传感器的测…...
unity学习第1天
本身也具有一些unity知识,包括Eidtor界面使用、Shader效果实现、性能分析,但对C#、游戏逻辑不太清楚,这次想从开发者角度理解游戏,提高C#编程,从简单的unity游戏理解游戏逻辑,更好的为工作服务。 unity201…...
Spring Boot实现对文件进行压缩下载
在Web应用中,文件下载功能是一个常见的需求,特别是当你需要提供用户下载各种类型的文件时。本文将演示如何使用Spring Boot框架来实现一个简单而强大的文件下载功能。我们将创建一个RESTful API,通过该API,用户可以下载问价为ZIP压…...
Mac专用投屏工具AirServer 7 .27 for Mac中文免费激活版
AirServer 7 .27 for Mac中文免费激活版是一款Mac专用投屏工具,能够通过本地网络将音频、照片、视频以及支持AirPlay功能的第三方App,从 iOS 设备无线传送到 Mac 电脑的屏幕上,把Mac变成一个AirPlay终端的实用工具。 目前最新的AirServer 7.2…...
LabVIEW使用巴特沃兹低通滤波器过滤噪声
LabVIEW使用巴特沃兹低通滤波器过滤噪声 设备采集到的数据往往都有噪声,有时候这些数据要做判断使用,如果不处理往往会影响最终的结果。可以使用动态平滑,或者中值滤波等方法。这里介绍使用巴特沃斯低通滤波,也是非常方便的。 下…...
【Realtek sdk-3.4.14b】RTL8197FH-VG和RTL8812F自适应认证失败问题分析及修改
WiFi自适应认证介绍 WiFi 自适应可以理解为针对WiFi的产品,当有外部干扰信号通过,WiFi产品自动停止发出信号一段时间,以达到避让的目的。 问题描述 2.4G和5G WiFi自适应认证失败,信道停止发送信号时间过长,没有在规定时间内停止发包 2.4G截图 问题分析 根据实验室描述可以…...
SpringBoot 的版本、打包、Maven
一、SpringBoot 结构、集成 1.1、集成组件 Spring Core:Spring的核心组件,提供IOC、AOP等基础功能,是Spring全家桶的基础。 Spring Boot:一个基于Spring Framework的快速开发框架,可以快速创建独立的、生产级别的…...
不同类型程序的句柄研究
先做一个winform程序;随便放几个控件; 用窗口句柄查看工具看一下;form和上面的每个控件都有一个句柄; 然后看一下记事本;记事本一共包含三个控件,各自有句柄; 这工具的使用是把右下角图标拖到要…...
【Godot】解决游戏中的孤立/孤儿节点及分析器性能问题的分析处理
Godot 4.1 因为我在游戏中发现,越运行游戏变得越来越卡,当你使用 Node 节点中的 print_orphan_nodes() 方法打印信息的时候,会出现如下的孤儿节点信息 孤儿节点信息是以 节点实例ID - Stray Node: 节点名称(Type: 节点类型) 作为格式输出&a…...
国家网络安全宣传周知识竞赛活动小程序界面分享
国家网络安全宣传周知识竞赛活动小程序界面分享...
mysql的判断语句
if if 用于做条件判断,具体的语法结构如下,在 if 条件判断的结构中, ELSE IF 结构可以有多个,也可以没有。 ELSE 结构可以有,也可以没有。 IF 条件1 THEN ..... ELSEIF 条件2 THEN -- 可选 ..... ELSE -- 可选 .....…...
ArcGIS Maps SDK for JavaScript系列之四:添加自定义底图
目录 Basemap类介绍Basemap类的常用属性Basemap类的常用方法 使用Basemap添加自定义底图引用Basemap引用切片图层创建一个新的Basemap对象将自定义图层应用到地图视图中引入并创建Camera对象引入并创建SceneView对象 Basemap类介绍 Basemap类是ArcGIS Maps SDK for JavaScript…...
Learn Prompt-角色扮演
模拟面试 当你在新闻中读到更多关于ChatGPT的内容时,你会听说ChatGPT可以代替医生、面试官、教师、律师等。但如果你想在实践中使用它,除了使用简单的提示或例子,你还可以根据不同的场景为ChatGPT设置不同的角色,这样我们就可以…...
《动手学深度学习 Pytorch版》 6.1 从全连接层到卷积
6.1.1 不变性 平移不变性(translation invariance): 不管检测对象出现在图像中的哪个位置,神经网络的前面几层应该对相同的图像区域具有相似的反应,即为“平移不变性”。 局部性(locality)&…...
六、数学建模之插值与拟合
1.概念 2.例题和matlab代码求解 一、概念 1.插值 (1)定义:插值是数学和统计学中的一种技术,用于估算在已知数据点之间的未知数据点的值。插值的目标是通过已知数据点之间的某种函数或方法来估计中间位置的数值。插值通常用于数…...
【项目经验】:elementui表格中数字汉字排序问题及字符串方法localeCompare()
一.需求 表格中数字汉字排序,数字按大小排列,汉字按拼音首字母(A-Z)排序。 二.用到的方法 第一步:把el-table-column上加上sortable"custom" <el-table-column prop"date" label"序号…...
Spring Boot的运行原理
Spring Boot的运行原理 Spring Boot是一个用于快速构建独立、可独立运行的Spring应用程序的框架。它通过自动配置和约定优于配置的原则,简化了Spring应用程序的开发过程。下面将详细介绍Spring Boot的运行原理,并附上一些代码解释。 1. 主要组件 Sprin…...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...
MySQL 主从同步异常处理
阅读原文:https://www.xiaozaoshu.top/articles/mysql-m-s-update-pk MySQL 做双主,遇到的这个错误: Could not execute Update_rows event on table ... Error_code: 1032是 MySQL 主从复制时的经典错误之一,通常表示ÿ…...
热门Chrome扩展程序存在明文传输风险,用户隐私安全受威胁
赛门铁克威胁猎手团队最新报告披露,数款拥有数百万活跃用户的Chrome扩展程序正在通过未加密的HTTP连接静默泄露用户敏感数据,严重威胁用户隐私安全。 知名扩展程序存在明文传输风险 尽管宣称提供安全浏览、数据分析或便捷界面等功能,但SEMR…...
