android camera的使用以及输出的图像格式

一、Camera

1.1、结合SurfaceView实现预览

1.1.1、布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".CameraActivity"><SurfaceViewandroid:id="@+id/preview"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"/>
</LinearLayout>

1.1.2、实现预览

mBinding.preview.getHolder().addCallback(new SurfaceHolder.Callback2() {@Overridepublic void surfaceRedrawNeeded(@NonNull SurfaceHolder holder) {mCamera = Camera.open();try {mCamera.setPreviewDisplay(holder);mCamera.startPreview();} catch (IOException e) {throw new RuntimeException(e);}}@Overridepublic void surfaceCreated(@NonNull SurfaceHolder holder) {}@Overridepublic void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {}@Overridepublic void surfaceDestroyed(@NonNull SurfaceHolder holder) {}});
  • Camera.open()
    打开摄像头
  • setPreviewDisplay
    设置预览展示的控件
  • startPreview
    开始预览
    在这里插入图片描述
    发现预览是横着的,需要使用setDisplayOrientation调整预览图像的方向
    在这里插入图片描述

1.1.3、获取摄像头的原始数据

mBinding.preview.getHolder().addCallback(new SurfaceHolder.Callback2() {@Overridepublic void surfaceRedrawNeeded(@NonNull SurfaceHolder holder) {mCamera = Camera.open();try {mCamera.setPreviewDisplay(holder);mCamera.setDisplayOrientation(90);mCamera.setPreviewCallback(new PreviewCallBack());mCamera.startPreview();} catch (IOException e) {throw new RuntimeException(e);}}@Overridepublic void surfaceCreated(@NonNull SurfaceHolder holder) {}@Overridepublic void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {}@Overridepublic void surfaceDestroyed(@NonNull SurfaceHolder holder) {}});
private class PreviewCallBack implements Camera.PreviewCallback {@Overridepublic void onPreviewFrame(byte[] data, Camera camera) {try {bos.write(data);Camera.Size previewSize = camera.getParameters().getPreviewSize();Log.e(TAG, "onPreviewFrame:" + previewSize.width + "x" + previewSize.height);Log.e(TAG, "Image format:" + camera.getParameters().getPictureFormat());} catch (IOException e) {throw new RuntimeException(e);}}}

在这里插入图片描述
在这里插入图片描述

  • setPreviewCallback
    设置预览数据的回调
  • 2560*1440
    默认返回图像的分辨率
  • Image format:256
    默认返回图像的数据格式是JPEG的
    在这里插入图片描述

1.1.4、调整相机数据的分辨率和格式

Parameters
 private Camera.Size getBestPreviewSize(Camera.Parameters parameters, int desiredWidth, int desiredHeight) {List<Camera.Size> supportedSizes = parameters.getSupportedPreviewSizes();Camera.Size bestSize = null;int bestDiff = Integer.MAX_VALUE;for (Camera.Size size : supportedSizes) {int diff = Math.abs(size.width - desiredWidth) + Math.abs(size.height - desiredHeight);if (diff < bestDiff) {bestSize = size;bestDiff = diff;}}return bestSize;}
                    mCamera = Camera.open();try {mCamera.setPreviewDisplay(holder);Camera.Parameters parameters = mCamera.getParameters();// 设置预览尺寸int desiredWidth = 1280; // 设置所需的宽度int desiredHeight = 720; // 设置所需的高度Camera.Size bestSize = getBestPreviewSize(parameters, desiredWidth, desiredHeight);parameters.setPreviewSize(bestSize.width, bestSize.height);parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);//设置输出数据的格式为NV21parameters.setPictureFormat(ImageFormat.NV21);mCamera.setParameters(parameters);mCamera.setPreviewCallback(new PreviewCallBack());mCamera.setDisplayOrientation(90);mCamera.startPreview();} catch (IOException e) {throw new RuntimeException(e);}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
ImageFormat里面还定义的有YUV_420_888(yuv420)、YV12,那我们可以使用吗?
在这里插入图片描述
在这里插入图片描述
运行后,应用直接抛出异常了,我用的真机是VIVO x20的,android版本是8.1的,所以,不支持设置输出的摄像头数据为YUV_420_888的。

1.1.5、保存摄像头输出的nv21格式的数据

package com.aniljing.androidcamera;import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.SurfaceHolder;import com.aniljing.androidcamera.databinding.ActivityCameraBinding;import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;public class CameraActivity extends AppCompatActivity {private final String TAG = CameraActivity.class.getSimpleName();private Context mContext;private ActivityCameraBinding mBinding;private Camera mCamera;private File mFile = new File(Environment.getExternalStorageDirectory(), "nv21.yuv");private BufferedOutputStream bos;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mContext = this;mBinding = ActivityCameraBinding.inflate(getLayoutInflater());setContentView(mBinding.getRoot());try {if (mFile.exists()) {mFile.delete();}bos = new BufferedOutputStream(new FileOutputStream(mFile));} catch (FileNotFoundException e) {throw new RuntimeException(e);}mBinding.preview.getHolder().addCallback(new SurfaceHolder.Callback2() {@Overridepublic void surfaceRedrawNeeded(@NonNull SurfaceHolder holder) {mCamera = Camera.open();try {mCamera.setPreviewDisplay(holder);Camera.Parameters parameters = mCamera.getParameters();// 设置预览尺寸int desiredWidth = 1280; // 设置所需的宽度int desiredHeight = 720; // 设置所需的高度Camera.Size bestSize = getBestPreviewSize(parameters, desiredWidth, desiredHeight);parameters.setPreviewSize(bestSize.width, bestSize.height);parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);parameters.setPictureFormat(ImageFormat.NV21);mCamera.setParameters(parameters);mCamera.setPreviewCallback(new PreviewCallBack());mCamera.setDisplayOrientation(90);mCamera.startPreview();} catch (IOException e) {throw new RuntimeException(e);}}@Overridepublic void surfaceCreated(@NonNull SurfaceHolder holder) {}@Overridepublic void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {}@Overridepublic void surfaceDestroyed(@NonNull SurfaceHolder holder) {}});}private class PreviewCallBack implements Camera.PreviewCallback {@Overridepublic void onPreviewFrame(byte[] data, Camera camera) {try {bos.write(data);Camera.Size previewSize = camera.getParameters().getPreviewSize();Log.e(TAG, "onPreviewFrame:" + previewSize.width + "x" + previewSize.height);Log.e(TAG,"Image format:"+camera.getParameters().getPictureFormat());} catch (IOException e) {throw new RuntimeException(e);}}}@Overrideprotected void onDestroy() {super.onDestroy();mCamera.setPreviewCallback(null);mCamera.stopPreview();mCamera.release();mCamera = null;try {bos.flush();bos.close();bos = null;} catch (IOException e) {throw new RuntimeException(e);}}private Camera.Size getBestPreviewSize(Camera.Parameters parameters, int desiredWidth, int desiredHeight) {List<Camera.Size> supportedSizes = parameters.getSupportedPreviewSizes();Camera.Size bestSize = null;int bestDiff = Integer.MAX_VALUE;for (Camera.Size size : supportedSizes) {int diff = Math.abs(size.width - desiredWidth) + Math.abs(size.height - desiredHeight);if (diff < bestDiff) {bestSize = size;bestDiff = diff;}}return bestSize;}
}
  • 保存为yuv格式的文件
    在这里插入图片描述

  • 使用YuvEye工具打开nv21.yuv文件
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 可以看出,虽然预览已经可以竖屏显示视频了,但是我们实际保存的数据却还是横屏的,所以就需要我们手动的调整

  • 如果我们选择其他格式的,使用工具是否能够正常打开呢?
    在这里插入图片描述
    在这里插入图片描述
    我们会发现,柜子的颜色和使用正确的格式有很明显的差别的

  • 如果我们输入错误的分辨率,又是什么效果呢?
    在这里插入图片描述
    在这里插入图片描述

1.1.6、数据旋转

java方式:

private void nv21_rotate_to_90(byte[] nv21_data, byte[] nv21_rotated, int width, int height) {int y_size = width * height;int buffser_size = y_size * 3 / 2;// Rotate the Y lumaint i = 0;int startPos = (height - 1) * width;for (int x = 0; x < width; x++) {int offset = startPos;for (int y = height - 1; y >= 0; y--) {nv21_rotated[i] = nv21_data[offset + x];i++;offset -= width;}}// Rotate the U and V color componentsi = buffser_size - 1;for (int x = width - 1; x > 0; x = x - 2) {int offset = y_size;for (int y = 0; y < height / 2; y++) {nv21_rotated[i] = nv21_data[offset + x];i--;nv21_rotated[i] = nv21_data[offset + (x - 1)];i--;offset += width;}}}
package com.aniljing.androidcamera;import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.SurfaceHolder;import com.aniljing.androidcamera.databinding.ActivityCameraBinding;import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;public class CameraActivity extends AppCompatActivity {private final String TAG = CameraActivity.class.getSimpleName();private Context mContext;private ActivityCameraBinding mBinding;private Camera mCamera;private File mFile = new File(Environment.getExternalStorageDirectory(), "nv21_rotated.yuv");private BufferedOutputStream bos;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mContext = this;mBinding = ActivityCameraBinding.inflate(getLayoutInflater());setContentView(mBinding.getRoot());try {if (mFile.exists()) {mFile.delete();}bos = new BufferedOutputStream(new FileOutputStream(mFile));} catch (FileNotFoundException e) {throw new RuntimeException(e);}mBinding.preview.getHolder().addCallback(new SurfaceHolder.Callback2() {@Overridepublic void surfaceRedrawNeeded(@NonNull SurfaceHolder holder) {mCamera = Camera.open();try {mCamera.setPreviewDisplay(holder);Camera.Parameters parameters = mCamera.getParameters();// 设置预览尺寸int desiredWidth = 1280; // 设置所需的宽度int desiredHeight = 720; // 设置所需的高度Camera.Size bestSize = getBestPreviewSize(parameters, desiredWidth, desiredHeight);parameters.setPreviewSize(bestSize.width, bestSize.height);parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);parameters.setPictureFormat(ImageFormat.NV21);mCamera.setParameters(parameters);mCamera.setPreviewCallback(new PreviewCallBack());mCamera.setDisplayOrientation(90);mCamera.startPreview();} catch (IOException e) {throw new RuntimeException(e);}}@Overridepublic void surfaceCreated(@NonNull SurfaceHolder holder) {}@Overridepublic void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {}@Overridepublic void surfaceDestroyed(@NonNull SurfaceHolder holder) {}});}private class PreviewCallBack implements Camera.PreviewCallback {@Overridepublic void onPreviewFrame(byte[] data, Camera camera) {try {Camera.Size previewSize = camera.getParameters().getPreviewSize();byte[] nv21_rotated=new byte[data.length];nv21_rotate_to_90(data,nv21_rotated, previewSize.width, previewSize.height);bos.write(nv21_rotated);Log.e(TAG, "onPreviewFrame:" + previewSize.width + "x" + previewSize.height);Log.e(TAG,"Image format:"+camera.getParameters().getPictureFormat());} catch (IOException e) {throw new RuntimeException(e);}}}@Overrideprotected void onDestroy() {super.onDestroy();mCamera.setPreviewCallback(null);mCamera.stopPreview();mCamera.release();mCamera = null;try {bos.flush();bos.close();bos = null;} catch (IOException e) {throw new RuntimeException(e);}}private Camera.Size getBestPreviewSize(Camera.Parameters parameters, int desiredWidth, int desiredHeight) {List<Camera.Size> supportedSizes = parameters.getSupportedPreviewSizes();Camera.Size bestSize = null;int bestDiff = Integer.MAX_VALUE;for (Camera.Size size : supportedSizes) {int diff = Math.abs(size.width - desiredWidth) + Math.abs(size.height - desiredHeight);if (diff < bestDiff) {bestSize = size;bestDiff = diff;}}return bestSize;}private void nv21_rotate_to_90(byte[] nv21_data, byte[] nv21_rotated, int width, int height) {int y_size = width * height;int buffser_size = y_size * 3 / 2;// Rotate the Y lumaint i = 0;int startPos = (height - 1) * width;for (int x = 0; x < width; x++) {int offset = startPos;for (int y = height - 1; y >= 0; y--) {nv21_rotated[i] = nv21_data[offset + x];i++;offset -= width;}}// Rotate the U and V color componentsi = buffser_size - 1;for (int x = width - 1; x > 0; x = x - 2) {int offset = y_size;for (int y = 0; y < height / 2; y++) {nv21_rotated[i] = nv21_data[offset + x];i--;nv21_rotated[i] = nv21_data[offset + (x - 1)];i--;offset += width;}}}
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 因为我们对数据进行了顺时针旋转了90度,所以,旋转后的图像数据的宽高也会发生颠倒

libYuv库(使用ubuntu编译libyuv库可以参考http://t.csdnimg.cn/W24Pr):

CMakeLists.txt

cmake_minimum_required(VERSION 3.22.1)project("androidcamera")#声明头文件路径
set(INCLUDE_DIR ${CMAKE_SOURCE_DIR}/../jniLibs/include)
#声明库文件路径
set(LIB_DIR ${CMAKE_SOURCE_DIR}/../jniLibs/lib)
#libYuv
set(YUV_LIB yuv)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}  -L${LIB_DIR}/${ANDROID_ABI} -std=gnu++11")#设置头文件
include_directories(${INCLUDE_DIR})add_library(${CMAKE_PROJECT_NAME} SHAREDnative-lib.cppYuvUtil.cpp)target_link_libraries(${CMAKE_PROJECT_NAME}#链接yuv库${YUV_LIB}androidlog)

YuvUtil.h

//
// Created by company on 2024-01-25.
//#ifndef ANDROIDCAMERA_YUVUTIL_H
#define ANDROIDCAMERA_YUVUTIL_H
#include <jni.h>
#include <libyuv.h>void nv21ToI420(jbyte *src_nv21_data,jint width,jint height,jbyte* dst_i420_data);
/*** i420旋转* @param src_i420_data* @param width* @param height* @param dst_i420_data* @param degree*/
void rotateI420(jbyte*src_i420_data,jint width,jint height,jbyte*dst_i420_data,jint degree);#endif //ANDROIDCAMERA_YUVUTIL_H

YuvUtil.cpp

//
// Created by company on 2024-01-25.
//#include "YuvUtil.h"void nv21ToI420(jbyte *src_nv21_data, jint width, jint height, jbyte *dst_i420_data) {jint src_y_size = width * height;jint src_u_size = (width >> 1) * (height >> 1);jbyte *src_nv21_y_data = src_nv21_data;jbyte *src_nv21_vu_data = src_nv21_data + src_y_size;jbyte *dst_i420_y_data = dst_i420_data;jbyte *dst_i420_u_data = dst_i420_data + src_y_size;jbyte *dst_i420_v_data = dst_i420_data + src_y_size + src_u_size;libyuv::NV21ToI420((const uint8_t *) src_nv21_y_data, width,(const uint8_t *) src_nv21_vu_data, width,(uint8_t *) dst_i420_y_data, width,(uint8_t *) dst_i420_u_data, width >> 1,(uint8_t *) dst_i420_v_data, width >> 1,width, height);
}void rotateI420(jbyte *src_i420_data, jint width, jint height, jbyte *dst_i420_data, jint degree) {jint src_i420_y_size = width * height;jint src_i420_u_size = (width >> 1) * (height >> 1);jbyte *src_i420_y_data = src_i420_data;jbyte *src_i420_u_data = src_i420_data + src_i420_y_size;jbyte *src_i420_v_data = src_i420_data + src_i420_y_size + src_i420_u_size;jbyte *dst_i420_y_data = dst_i420_data;jbyte *dst_i420_u_data = dst_i420_data + src_i420_y_size;jbyte *dst_i420_v_data = dst_i420_data + src_i420_y_size + src_i420_u_size;if (degree == libyuv::kRotate90 || degree == libyuv::kRotate270) {//90或270的width和height在旋转后是相反的libyuv::I420Rotate((const uint8_t *) src_i420_y_data, width,(const uint8_t *) src_i420_u_data, width >> 1,(const uint8_t *) src_i420_v_data, width >> 1,(uint8_t *) dst_i420_y_data, height,(uint8_t *) dst_i420_u_data, height >> 1,(uint8_t *) dst_i420_v_data, height >> 1,width, height, (libyuv::RotationMode) degree);} else {libyuv::I420Rotate((const uint8_t *) src_i420_y_data, width,(const uint8_t *) src_i420_u_data, width >> 1,(const uint8_t *) src_i420_v_data, width >> 1,(uint8_t *) dst_i420_y_data, width,(uint8_t *) dst_i420_u_data, width >> 1,(uint8_t *) dst_i420_v_data, width >> 1,width, height, (libyuv::RotationMode) degree);}
}

native-lib.cpp

#include <jni.h>
#include <string>
#include "YuvUtil.h"extern "C"
JNIEXPORT void JNICALL
Java_com_aniljing_androidcamera_YuvUtil_nv21ToI420(JNIEnv *env, jobject thiz,jbyteArray src_nv21_array, jint width,jint height, jbyteArray dst_i420_array) {jbyte *src_nv21_data = env->GetByteArrayElements(src_nv21_array, JNI_FALSE);jbyte *dst_i420_data = env->GetByteArrayElements(dst_i420_array, JNI_FALSE);nv21ToI420(src_nv21_data, width, height, dst_i420_data);env->ReleaseByteArrayElements(src_nv21_array, src_nv21_data, 0);env->ReleaseByteArrayElements(dst_i420_array, dst_i420_data, 0);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_aniljing_androidcamera_YuvUtil_i420Rotate(JNIEnv *env, jobject thiz,jbyteArray src_i420_array, jint width,jint height, jbyteArray dst_i420_array,jint degree) {jbyte *src_i420_data = env->GetByteArrayElements(src_i420_array, JNI_FALSE);jbyte *dst_i420_data = env->GetByteArrayElements(dst_i420_array, JNI_FALSE);rotateI420(src_i420_data, width, height, dst_i420_data, degree);env->ReleaseByteArrayElements(src_i420_array, src_i420_data, 0);env->ReleaseByteArrayElements(dst_i420_array, dst_i420_data, 0);
}

YuvUtil.java

package com.aniljing.androidcamera;public class YuvUtil {public native void nv21ToI420(byte[] src_nv21_data, int width, int height, byte[] dst_i420_data);public native void i420Rotate(byte[] src_i420_data, int width, int height, byte[] dst_i420_data, int degree);static {System.loadLibrary("androidcamera");}
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 格式选择 I420
  • 分辨率设置为720*1280

二、Camera2

Camera2(android.hardware.camera2)是从 Android 5.0 L 版本开始引入的,并且废弃了旧的相机框架Camera1(android.hardware.Camera)。

相比于Camera1,Camera2架构上也发生了变化,API上的使用难度也增加了。Camera2将相机设备模拟成一个管道,它按顺序处理每一帧的请求并返回请求结果给客户端。
在这里插入图片描述

2.1、核心类

CameraManager

相机系统服务,用于管理和连接相机设备

CameraDevice

相机设备类,和Camera1中的Camera同级

CameraCharacteristics

主要用于获取相机信息,内部携带大量的相机信息,包含摄像头的正反(LENS_FACING)、AE模式、AF模式等,和Camera1中的Camera.Parameters类似

CaptureRequest

相机捕获图像的设置请求,包含传感器,镜头,闪光灯等

CaptureRequest.Builder

CaptureRequest的构造器,使用Builder模式,设置更加方便

CameraCaptureSession

请求抓取相机图像帧的会话,会话的建立主要会建立起一个通道。一个CameraDevice一次只能开启一个CameraCaptureSession。源端是相机,另一端是 Target,Target可以是Preview,也可以是ImageReader。

ImageReader

用于从相机打开的通道中读取需要的格式的原始图像数据,可以设置多个ImageReader。

2.2、使用流程

2.2.1、获取CameraManager

CameraManager cameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);

2.2.2、获取相机信息,并打开相机

try {String[] cameraIds = cameraManager.getCameraIdList();for (int i = 0; i < cameraIds.length; i++) {//描述相机设备的属性类CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraIds[i]);Range<Integer>[] allFpsRanges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);fpsRanges = allFpsRanges[allFpsRanges.length - 1];//获取是前置还是后置摄像头Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);//使用后置摄像头if (facing != null && facing == CameraCharacteristics.LENS_FACING_BACK) {StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);//寻找一个 最合适的尺寸     ---》 一模一样mPreviewSize = getBestSupportedSize(new ArrayList<Size>(Arrays.asList(map.getOutputSizes(SurfaceTexture.class))));if (map != null) {mCameraId = cameraIds[i];}orientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);//maxImages 此时需要2路,一路渲染到屏幕,一路用于网络传输mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),ImageFormat.YUV_420_888, 2);mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mCameraHandler);cameraManager.openCamera(mCameraId, mCameraDeviceStateCallback, mCameraHandler);}}} catch (CameraAccessException r) {Log.e(TAG, "openCamera:" + r);Log.e(TAG, "openCamera getMessage:" + r.getMessage());Log.e(TAG, "openCamera getLocalizedMessage:" + r.getLocalizedMessage());}
  • ImageReader
    ImageReader是获取图像数据的重要途径,通过它可以获取到不同格式的图像数据,例如JPEG、YUV、RAW等。通过ImageReader.newInstance(int width, int height, int format, int maxImages)创建ImageReader对象,有4个参数:

    • width:图像数据的宽度
    • height:图像数据的高度
    • format:图像数据的格式,例如ImageFormat.JPEG,ImageFormat.YUV_420_888等
    • maxImages:最大Image个数,Image对象池的大小,指定了能从ImageReader获取Image对象的最大值,过多获取缓冲区可能导致OOM,所以最好按照最少的需要去设置这个值

    ImageReader其他相关的方法和回调:

    • ImageReader.OnImageAvailableListener:有新图像数据的回调
    • acquireLatestImage():从ImageReader的队列里面,获取最新的Image,删除旧的,如果没有可用的Image,返回null
    • acquireNextImage():获取下一个最新的可用Image,没有则返回null
    • close():释放与此ImageReader关联的所有资源
    • getSurface():获取为当前ImageReader生成Image的Surface
  • 打开相机设备
    cameraManager.openCamera(@NonNull String cameraId,@NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)的三个参数:

    • cameraId:摄像头的唯一标识
    • callback:设备连接状态变化的回调
    • handler:回调执行的Handler对象,传入null则使用当前的主线程Handler

2.2.3、创建CaptureRequest

private final CameraDevice.StateCallback mCameraDeviceStateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(CameraDevice camera) {try {mCameraDevice = camera;SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();surfaceTexture.setDefaultBufferSize(1280, 720);//Surface负责渲染Surface previewSurface = new Surface(surfaceTexture);//创建请求mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);mPreviewBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRanges);mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);mPreviewBuilder.addTarget(previewSurface);mPreviewBuilder.addTarget(mImageReader.getSurface());//创建会话mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, mImageReader.getSurface()), mCaptureSessionStateCallBack, mCameraHandler);} catch (CameraAccessException e) {e.printStackTrace();Log.e(TAG, "onOpened:" + e.getMessage());Log.e(TAG, "onOpened:" + e.getLocalizedMessage());Log.e(TAG, "onOpened:" + e);}}@Overridepublic void onDisconnected(CameraDevice camera) {Log.e(TAG, "onDisconnected");camera.close();mCameraDevice = null;}@Overridepublic void onError(CameraDevice camera, int error) {Log.e(TAG, "onError:" + error);camera.close();mCameraDevice = null;}};

CameraDevice.StateCallback的onOpened回调中执行:这段的代码核心方法是mCameraDevice.createCaptureSession()创建Capture会话,它接受了三个参数:

  • outputs:用于接受图像数据的surface集合,这里传入的是一个preview的surface
  • callback:用于监听 Session 状态的CameraCaptureSession.StateCallback对象
  • handler:用于执行CameraCaptureSession.StateCallback的Handler对象,传入null则使用当前的主线程Handler

通过CameraDevice.createCaptureRequest()创建CaptureRequest.Builder对象,传入一个templateType参数,templateType用于指定使用何种模板创建CaptureRequest.Builder对象,templateType的取值:

  • TEMPLATE_PREVIEW:预览模式
  • TEMPLATE_STILL_CAPTURE:拍照模式
  • TEMPLATE_RECORD:视频录制模式
  • TEMPLATE_VIDEO_SNAPSHOT:视频截图模式
  • TEMPLATE_MANUAL:手动配置参数模式

2.2.4、预览

   private final CameraCaptureSession.StateCallback mCaptureSessionStateCallBack = new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(CameraCaptureSession session) {try {mCaptureSession = session;CaptureRequest request = mPreviewBuilder.build();// Finally, we start displaying the camera preview.session.setRepeatingRequest(request, null, mCameraHandler);} catch (CameraAccessException e) {e.printStackTrace();}}@Overridepublic void onConfigureFailed(CameraCaptureSession session) {Log.e(TAG, "onConfigureFailed:");}};

在CameraCaptureSession.StateCallback回调中,Camera2通过连续重复的Capture实现预览功能,每次Capture会把预览画面显示到对应的Surface上。连续重复的Capture操作通过mCaptureSession.setRepeatingRequest(mPreviewRequest,mCaptureCallback, mBackgroundHandler)实现,该方法有三个参数:

  • request:CaptureRequest对象
  • listener:监听Capture 状态的回调
  • handler:用于执行CameraCaptureSession.CaptureCallback的Handler对象,传入null则使用当前的主线程Handler

停止预览使用mCaptureSession.stopRepeating()方法。

2.3、完整实现代码

package com.aniljing.androidcamera;import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Range;
import android.util.Size;
import android.view.Surface;
import android.view.TextureView;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** @ClassName Camera2ProviderPreviewWithYUV* Camera2 两路预览:* 1、使用TextureView预览,直接输出。* 2、使用ImageReader获取数据,输出格式为ImageFormat.YUV_420_888,java端转化为NV21*/
public class Camera2WithYUV {private static final String TAG = Camera2WithYUV.class.getSimpleName();private Activity mContext;private String mCameraId;private HandlerThread handlerThread;private Handler mCameraHandler;private CameraDevice mCameraDevice;private TextureView mTextureView;private CaptureRequest.Builder mPreviewBuilder;private CameraCaptureSession mCaptureSession;private ImageReader mImageReader;private Size mPreviewSize;private final Point previewViewSize;private Range<Integer> fpsRanges;private byte[] i420;private YUVDataCallBack mYUVDataCallBack;private int orientation;private YuvUtil mYuvUtil;public Camera2WithYUV(Activity mContext) {this.mContext = mContext;handlerThread = new HandlerThread("camera");handlerThread.start();mCameraHandler = new Handler(handlerThread.getLooper());previewViewSize = new Point();previewViewSize.x = 640;previewViewSize.y = 480;mYuvUtil=new YuvUtil();}public void initTexture(TextureView textureView) {Log.e(TAG, "initTexture:" + textureView);mTextureView = textureView;mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {openCamera();}@Overridepublic void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}@Overridepublic boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {return true;}@Overridepublic void onSurfaceTextureUpdated(SurfaceTexture surface) {}});}@SuppressLint("MissingPermission")private void openCamera() {CameraManager cameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);try {String[] cameraIds = cameraManager.getCameraIdList();for (int i = 0; i < cameraIds.length; i++) {//描述相机设备的属性类CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraIds[i]);Range<Integer>[] allFpsRanges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);fpsRanges = allFpsRanges[allFpsRanges.length - 1];//获取是前置还是后置摄像头Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);//使用后置摄像头if (facing != null && facing == CameraCharacteristics.LENS_FACING_BACK) {StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);//寻找一个 最合适的尺寸     ---》 一模一样mPreviewSize = getBestSupportedSize(new ArrayList<Size>(Arrays.asList(map.getOutputSizes(SurfaceTexture.class))));if (map != null) {mCameraId = cameraIds[i];}orientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);//maxImages 此时需要2路,一路渲染到屏幕,一路用于网络传输mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),ImageFormat.YUV_420_888, 2);mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mCameraHandler);cameraManager.openCamera(mCameraId, mCameraDeviceStateCallback, mCameraHandler);}}} catch (CameraAccessException r) {Log.e(TAG, "openCamera:" + r);Log.e(TAG, "openCamera getMessage:" + r.getMessage());Log.e(TAG, "openCamera getLocalizedMessage:" + r.getLocalizedMessage());}}private final CameraDevice.StateCallback mCameraDeviceStateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(CameraDevice camera) {try {mCameraDevice = camera;SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();surfaceTexture.setDefaultBufferSize(1280, 720);//Surface负责渲染Surface previewSurface = new Surface(surfaceTexture);//创建请求mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);mPreviewBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRanges);mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);mPreviewBuilder.addTarget(previewSurface);mPreviewBuilder.addTarget(mImageReader.getSurface());//创建会话mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, mImageReader.getSurface()), mCaptureSessionStateCallBack, mCameraHandler);} catch (CameraAccessException e) {e.printStackTrace();Log.e(TAG, "onOpened:" + e.getMessage());Log.e(TAG, "onOpened:" + e.getLocalizedMessage());Log.e(TAG, "onOpened:" + e);}}@Overridepublic void onDisconnected(CameraDevice camera) {Log.e(TAG, "onDisconnected");camera.close();mCameraDevice = null;}@Overridepublic void onError(CameraDevice camera, int error) {Log.e(TAG, "onError:" + error);camera.close();mCameraDevice = null;}};private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {@SuppressLint("LongLogTag")@Overridepublic void onImageAvailable(ImageReader reader) {Image image = reader.acquireNextImage();// Y:U:V == 4:2:2if (mYUVDataCallBack != null && image.getFormat() == ImageFormat.YUV_420_888) {Rect crop = image.getCropRect();int format = image.getFormat();int width = crop.width();int height = crop.height();if (i420 == null) {i420 = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];}i420 = getI420(image);if (mYUVDataCallBack != null) {mYUVDataCallBack.yuvData(i420, width, height, orientation);}}image.close();}};private final CameraCaptureSession.StateCallback mCaptureSessionStateCallBack = new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(CameraCaptureSession session) {try {mCaptureSession = session;CaptureRequest request = mPreviewBuilder.build();// Finally, we start displaying the camera preview.session.setRepeatingRequest(request, null, mCameraHandler);} catch (CameraAccessException e) {e.printStackTrace();}}@Overridepublic void onConfigureFailed(CameraCaptureSession session) {Log.e(TAG, "onConfigureFailed:");}};public void releaseCamera() {Log.e(TAG, "releaseCamera");if (mCaptureSession != null) {mCaptureSession.close();mCaptureSession = null;}if (mCameraDevice != null) {mCameraDevice.close();mCameraDevice = null;}if (mPreviewBuilder != null) {mPreviewBuilder = null;}if (mImageReader != null) {mImageReader.setOnImageAvailableListener(null, null);mImageReader.close();mImageReader = null;}if (handlerThread != null && !handlerThread.isInterrupted()) {handlerThread.quit();handlerThread.interrupt();handlerThread = null;}if (mCameraHandler != null) {mCameraHandler = null;}}public interface YUVDataCallBack {void yuvData(byte[] data, int width, int height, int orientation);}public void setYUVDataCallBack(YUVDataCallBack YUVDataCallBack) {mYUVDataCallBack = YUVDataCallBack;}private Size getBestSupportedSize(List<Size> sizes) {Point maxPreviewSize = new Point(640, 480);Point minPreviewSize = new Point(480, 320);Size defaultSize = sizes.get(0);Size[] tempSizes = sizes.toArray(new Size[0]);Arrays.sort(tempSizes, (o1, o2) -> {if (o1.getWidth() > o2.getWidth()) {return -1;} else if (o1.getWidth() == o2.getWidth()) {return o1.getHeight() > o2.getHeight() ? -1 : 1;} else {return 1;}});sizes = new ArrayList<>(Arrays.asList(tempSizes));for (int i = sizes.size() - 1; i >= 0; i--) {if (maxPreviewSize != null) {if (sizes.get(i).getWidth() > maxPreviewSize.x || sizes.get(i).getHeight() > maxPreviewSize.y) {sizes.remove(i);continue;}}if (minPreviewSize != null) {if (sizes.get(i).getWidth() < minPreviewSize.x || sizes.get(i).getHeight() < minPreviewSize.y) {sizes.remove(i);}}}if (sizes.size() == 0) {return defaultSize;}Size bestSize = sizes.get(0);float previewViewRatio;if (previewViewSize != null) {previewViewRatio = (float) previewViewSize.x / (float) previewViewSize.y;} else {previewViewRatio = (float) bestSize.getWidth() / (float) bestSize.getHeight();}if (previewViewRatio > 1) {previewViewRatio = 1 / previewViewRatio;}for (Size s : sizes) {if (Math.abs((s.getHeight() / (float) s.getWidth()) - previewViewRatio) < Math.abs(bestSize.getHeight() / (float) bestSize.getWidth() - previewViewRatio)) {bestSize = s;}}return bestSize;}private byte[] getI420(Image image) {try {int w = image.getWidth(), h = image.getHeight();// size是宽乘高的1.5倍 可以通过ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888)得到int i420Size = w * h * 3 / 2;Image.Plane[] planes = image.getPlanes();//remaining0 = rowStride*(h-1)+w => 27632= 192*143+176 Y分量byte数组的sizeint remaining0 = planes[0].getBuffer().remaining();int remaining1 = planes[1].getBuffer().remaining();//remaining2 = rowStride*(h/2-1)+w-1 =>  13807=  192*71+176-1 V分量byte数组的sizeint remaining2 = planes[2].getBuffer().remaining();//获取pixelStride,可能跟width相等,可能不相等int pixelStride = planes[2].getPixelStride();int rowOffest = planes[2].getRowStride();byte[] nv21 = new byte[i420Size];//分别准备三个数组接收YUV分量。byte[] yRawSrcBytes = new byte[remaining0];byte[] uRawSrcBytes = new byte[remaining1];byte[] vRawSrcBytes = new byte[remaining2];planes[0].getBuffer().get(yRawSrcBytes);planes[1].getBuffer().get(uRawSrcBytes);planes[2].getBuffer().get(vRawSrcBytes);if (pixelStride == image.getWidth()) {//两者相等,说明每个YUV块紧密相连,可以直接拷贝System.arraycopy(yRawSrcBytes, 0, nv21, 0, rowOffest * h);System.arraycopy(vRawSrcBytes, 0, nv21, rowOffest * h, rowOffest * h / 2 - 1);} else {//根据每个分量的size先生成byte数组byte[] ySrcBytes = new byte[w * h];byte[] uSrcBytes = new byte[w * h / 2 - 1];byte[] vSrcBytes = new byte[w * h / 2 - 1];for (int row = 0; row < h; row++) {//源数组每隔 rowOffest 个bytes 拷贝 w 个bytes到目标数组System.arraycopy(yRawSrcBytes, rowOffest * row, ySrcBytes, w * row, w);//y执行两次,uv执行一次if (row % 2 == 0) {//最后一行需要减一if (row == h - 2) {System.arraycopy(vRawSrcBytes, rowOffest * row / 2, vSrcBytes, w * row / 2, w - 1);} else {System.arraycopy(vRawSrcBytes, rowOffest * row / 2, vSrcBytes, w * row / 2, w);}}}//yuv拷贝到一个数组里面System.arraycopy(ySrcBytes, 0, nv21, 0, w * h);System.arraycopy(vSrcBytes, 0, nv21, w * h, w * h / 2 - 1);byte[] i420 = new byte[nv21.length];mYuvUtil.nv21ToI420(nv21, w, h, i420);return i420;}} catch (Exception e) {e.printStackTrace();}return null;}
}

源码

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

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

相关文章

burp靶场--CSRF

burp靶场–CSRF https://portswigger.net/web-security/csrf#what-is-csrf ### 什么是 CSRF&#xff1f; 跨站请求伪造&#xff08;也称为 CSRF&#xff09;是一种 Web 安全漏洞&#xff0c;允许攻击者诱导用户执行他们不打算执行的操作。它允许攻击者部分规避同源策略&#…

Unity 解决异步分发方案

很多程序&#xff0c;包括游戏、小程序、一些AR、VR的程序&#xff0c;因为客户端体量太大&#xff0c;更新频繁都涉及到远程热更新的问题&#xff0c;解决这类问题的思路基本上是客户端解决主要功能&#xff0c;资源类放置在服务器。 下面记录下&#xff1a; 1.CDN或者云轻量…

Windows11操作系统百科

简介 Windows 11是由微软公司&#xff08;Microsoft&#xff09;开发的操作系统&#xff0c;应用于计算机和平板电脑等设备 [1]。于2021年6月24日发布 [3]&#xff0c;2021年10月5日发行 [29]。 Windows 11提供了许多创新功能&#xff0c;增加了新版开始菜单和输入逻辑等 [6]…

安装ddddocr中遇到的问题

1、需要先安装&#xff1a; pip3 install pyinstaller --no-use-pep517 pip install scikit-build pip install setuptools pip install pyinstaller pip install pillow 重要是的是保证一个python 环境&#xff0c;多个python环境会导致各种问题。并且保证python>3.8…

安装宝塔面板后k8s所在节点pod无法正常工作解决方法,kubernetes k8s 与宝塔面板冲突解决方法

在实际项目过程中我们使用了k8s 在生产环境中运行管理服务。 但是对服务器的状态管理我们使用了宝塔面板进行 K8s 版本1.2.8 宝塔面板 版本 8.05 操作步骤是这样的。 1.完成1.2.8 k8s的节点安装&#xff0c;并正常运行服务。 过程略 2.安装宝塔面板 ​ yum install -y …

基于springboot留守儿童爱心网站源码和论文

随着留守儿童爱心管理的不断发展&#xff0c;留守儿童爱心网站在现实生活中的使用和普及&#xff0c;留守儿童爱心管理成为近年内出现的一个热门话题&#xff0c;并且能够成为大众广为认可和接受的行为和选择。设计留守儿童爱心网站的目的就是借助计算机让复杂的管理操作变简单…

Docker的Cgroup资源限制

目录 前瞻 CPU 资源控制 设置CPU使用率上限 设置CPU资源占用比&#xff08;设置多个容器时才有效&#xff09; 设置容器绑定指定的CPU 内存资源限制 对磁盘IO配额控制&#xff08;blkio&#xff09;的限制 前瞻 Docker 通过 Cgroup 来控制容器使用的资源配额&#xff0c…

让抖音引流到微信小程序的三方工具数灵通

抖音作为一款火爆的短视频社交平台&#xff0c;吸引了数亿用户的关注和喜爱。除了观看和制作视频外&#xff0c;抖音还提供了跳转到小程序的功能&#xff0c;让用户可以享受更多功能和乐趣。那么&#xff0c;如何在抖音中跳转到小程序呢&#xff1f;以下是详细解答&#xff1a;…

SpringBoot使用druid

SpringBoot使用druid 一、前言二、配置1、pom依赖2、配置文件yml3、配置类 一、前言 Java程序很大一部分要操作数据库&#xff0c;为了提高性能操作数据库的时候&#xff0c;又不得不使用数据库连接池。 Druid 是阿里巴巴开源平台上一个数据库连接池实现&#xff0c;结合了 C…

leetcode:排序链表(递归)

题目&#xff1a; 给定链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4]示例 2&#xff1a; 输入&#xff1a;head [-1,5,3,4,0] 输出&#xff1a;[-1,0,3,4,5]示例…

【华为 ICT HCIA eNSP 习题汇总】——题目集8

1、在VRP平台下&#xff0c;关于各个协议的外部优先级的描述&#xff0c;正确的是&#xff08;&#xff09;。 A、OSPF路由的外部优先级是15 B、IS-IS路由的外部优先级是10 C、静态路由的外部优先级是60 D、BGP路由的外部优先级是20 考点&#xff1a;路由技术原理 解析&#xf…

钉钉企业机器人单聊消息发送实践-大数据平台(XSailboat)消息中心消息推送

1. 背景 在笔者开发的大数据平台XSailboat中有 消息中心 模块&#xff0c;用来全平台的消息收集&#xff0c;整理分拆、订阅发送等功能。消息推送方式支持钉钉群聊、钉钉单聊、短信通知。现记录一下企业机器人消息单聊推送的实现过程。 2. 钉钉开发文档 这是官方的开发文档地…

MyBatis中一级缓存是什么?SqlSession一级缓存失效的原因?如何理解一级缓存?

一级缓存是SqlSession级别的&#xff0c;通过同一个SqlSession查询的数据会被缓存&#xff0c;下次查询相同的数据&#xff0c;就 会从缓存中直接获取&#xff0c;不会从数据库重新访问 使一级缓存失效的四种情况&#xff1a; 1) 不同的SqlSession对应不同的一级缓存 2) 同一…

好书推荐丨豆瓣评出9.2高分!Python编程入门就看蟒蛇书

目录 写在前面 内容简介 业内专家推荐 编辑推荐 资源丰富 作者介绍 Q&A 粉丝福利 写在后面 写在前面 在这日新月异的科技新时代&#xff0c;编程如同一把万能钥匙&#xff0c;为无数人打开了通向无限可能的大门。而在众多编程语言中&#xff0c;Python无疑是最耀…

etcd未授权到控制k8s集群

在安装完 K8s 后&#xff0c;默认会安装 etcd 组件&#xff0c;etcd 是一个高可用的 key-value 数据库&#xff0c;它为 k8s 集群提供底层数据存储&#xff0c;保存了整个集群的状态。大多数情形下&#xff0c;数据库中的内容没有加密&#xff0c;因此如果黑客拿下 etcd&#x…

防御课程—华为USG6000V1的配置实验(一)

实验拓扑&#xff1a; 实验分析 由实验拓扑图需求分析可知我们在生产区和办公区需要用到子接口技术 实验配置 在Cloud1上配置 在DMZ区域配置 在server1上配置在server2上配置在防火墙上进行的配置 由实验拓扑图可知防火墙与DMZ区域相连的接口为GigabitEthernet1/0/0接口 …

Pandas.Series.median() 中位数 详解 含代码 含测试数据集 随Pandas版本持续更新

关于Pandas版本&#xff1a; 本文基于 pandas2.2.0 编写。 关于本文内容更新&#xff1a; 随着pandas的stable版本更迭&#xff0c;本文持续更新&#xff0c;不断完善补充。 传送门&#xff1a; Pandas API参考目录 传送门&#xff1a; Pandas 版本更新及新特性 传送门&…

MySQL(基础篇)——SQL

一.SQL分类 二.DDL(数据定义语言) 1.DDL——数据库操作 ① 查询 查询所有数据库 SHOW DATABASES 查询当前所处数据库 SELECT DATABASE() ② 创建 CREATE DATABASE [IF NOT EXISTS] 数据库名(通常以db结尾) [DEFAULT CHARSET 字符集] [COLLATE 排序规则] ③ …

java分布式锁详解

本地锁 浏览器把100w请求由网关随机往下传&#xff0c;在集群情况下&#xff0c;每台服务都放行10w请求过来&#xff0c;这时候每台服务都用的是本地锁是跨JVM的&#xff0c; 列如这些服务都没有49企业&#xff0c;此时有几个服务进行回原了打击在DB上面&#xff0c;那后期把这…

Allegro PCB如何关联原理图?

在用Allegro进行PCB设计时,我们可以点击Orcad原理图上的器件,然后PCB会自动跳转到该器件。那如何操作PCB上的器件点击跳转到原理图呢? 这种方式可以提高设计的效率。具体操作如下。 选择菜单栏Display