Android 使用Camera2 实现拍照录像的功能

职场小白迷上优美句子:

还是电影  《无问西东》中的台词,这句有点感人:

沈光耀的妈妈对沈光耀说:"当初你离家千里,来到这个地方读书,你父亲和我都没有反对过,因为,是我们想你,能享受到人生的乐趣,比如读万卷书行万里路,比如同你喜欢的女孩子结婚生子。注意不是给我增添子孙,而是你自己,能够享受为人父母的乐趣,你一生所要追求的功名利禄,没有什么是你的祖上没经历过的,那些只不过是人生的幻光。我怕,你还没想好怎么过这一生,你的命就没了啊!"

推荐两篇对Camera2的讲解非常详细的博客 

新根 - - - Android Camera2 API和拍照与录像过程

Camera2.0新API下的摄像头预览、原始图像数据获取等_黄政的博客-CSDN博客


效果图:

 源码地址:

github开源相机Camera2源码https://github.com/13767004362/Camera2App.git

拍照操作类代码

public class PictureOperater extends BaseCamera2Operator {private static final String TAG = PictureOperater.class.getSimpleName();private WorkThreadUtils workThreadManager;/*** 相机最大的预览宽度*/private static final int MAX_PREVIEW_WIDTH = 1920;/*** 相机最大的预览高度*/private static final int MAX_PREVIEW_HEIGHT = 1080;/*** 处理静态图片的输出*/private ImageReader imageReader;/*** 相机传感器方向*/private int mSensorOrientation;/*** 相机预览的大小Size*/private Size mPreviewSize;/*** 是否支持自动对焦*/private boolean mAutoFocusSupported;/*** 是否支持闪光灯*/private boolean mFlashSupported;/*** 相机的Id*/private String mCameraId;/*** 预览请求的Builder*/private CaptureRequest.Builder mPreviewRequestBuilder;/*** 预览的请求*/private CaptureRequest mPreviewRequest;private CameraCaptureSession mCaptureSession;/*** 开启相机的锁住时间*/private final int LOCK_TIME = 2500;private Camera2Manager camera2Manager;/*** 最小的焦距*/private float minimalFocalDistance = 0;/*** 最大的数字变焦值,缩放值*/private float maxZoom = 0;public PictureOperater(Camera2Manager camera2Manager) {this.camera2Manager = camera2Manager;this.workThreadManager = camera2Manager.getWorkThreadManager();}@Overridepublic void startOperate() {TextureView textureView = getTextureView();if (textureView.isAvailable()) {openCamera(getTextureViewContext(), textureView.getWidth(), textureView.getHeight());} else {textureView.setSurfaceTextureListener(mSurfaceTextureListener);}}@Overridepublic void stopOperate() {Log.i(TAG, TAG+" 关闭相机的操作 ");closeCamera();}@Overridepublic void openCamera(Activity activity, int width, int height) {if (PermissionsManager.checkCameraPermission(activity)) {setUpCameraOutputs(activity, width, height);configureTransform(activity, width, height);CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);try {//打开相机需要一定时间,因锁住2.5秒,防止程序退出关闭相机if (!mCameraOpenCloseLock.tryAcquire(LOCK_TIME, TimeUnit.MILLISECONDS)) {throw new RuntimeException("Time out waiting to lock camera opening.");}manager.openCamera(mCameraId, stateCallback, workThreadManager.getBackgroundHandler());} catch (CameraAccessException e) {e.printStackTrace();} catch (InterruptedException e) {throw new RuntimeException("Interrupted while trying to lock camera opening.", e);}}}@Overridepublic void closePreviewSession() {if (mCaptureSession != null) {mCaptureSession.close();mCaptureSession = null;}}@Overridepublic void configureTransform(Activity activity, int viewWidth, int viewHeight) {if (null == getTextureView() || null == mPreviewSize || null == activity) {return;}int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();Matrix matrix = new Matrix();RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());float centerX = viewRect.centerX();float centerY = viewRect.centerY();if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);float scale = Math.max((float) viewHeight / mPreviewSize.getHeight(), (float) viewWidth / mPreviewSize.getWidth());matrix.postScale(scale, scale, centerX, centerY);matrix.postRotate(90 * (rotation - 2), centerX, centerY);} else if (Surface.ROTATION_180 == rotation) {matrix.postRotate(180, centerX, centerY);}getTextureView().setTransform(matrix);}@Overridepublic void writePictureData(Image image) {if (camera2ResultCallBack != null) {camera2ResultCallBack.callBack(ObservableBuilder.createWriteCaptureImage(appContext, image));}}@Overridepublic void startPreView() {//开启相机预览界面createCameraPreviewSession();}@Overridepublic void cameraClick() {takePicture();}/*** 关闭当前的相机设备,释放资源*/private void closeCamera() {try {mCameraOpenCloseLock.acquire();closePreviewSession();if (null != mCameraDevice) {mCameraDevice.close();mCameraDevice = null;}if (null != imageReader) {imageReader.close();imageReader = null;}} catch (InterruptedException e) {throw new RuntimeException("Interrupted while trying to lock camera closi" + "ng.", e);} finally {mCameraOpenCloseLock.release();}}/*** 拍照*/public void takePicture() {ToastUtils.showToast(camera2Manager.getContext()," 客官,请别抖动,正在拍照中。");if (mAutoFocusSupported) {Log.i(TAG,TAG+"支持自动调焦,正在锁住焦点");lockFocus();} else {//设备不支持自动对焦,则直接拍照。Log.i(TAG,TAG+"不支持自动调焦,直接拍照");captureStillPicture();}}/*** 拍照的第一步,锁住焦点。*/private void lockFocus() {try {//告诉相机,这里已经锁住焦点mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);// 标识,正在进行拍照动作mState = STATE_WAITING_LOCK;//进行拍照处理mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, workThreadManager.getBackgroundHandler());} catch (CameraAccessException e) {e.printStackTrace();}}/*** 相机开始预览,创建一个CameraCaptureSession对象*/private void createCameraPreviewSession() {try {SurfaceTexture texture = getTextureView().getSurfaceTexture();//assert断言表达式,为true继续。assert texture != null;// We configure the size of default buffer to be the size of camera preview we want.texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());// 创建一个预览界面需要用到的Surface对象Surface surface = new Surface(texture);// 将CaptureRequest的构建器与Surface对象绑定在一起mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);mPreviewRequestBuilder.addTarget(surface);// 为相机预览,创建一个CameraCaptureSession对象mCameraDevice.createCaptureSession(Arrays.asList(surface, imageReader.getSurface()), new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {// 相机已经关闭if (null == mCameraDevice) {return;}//当cameraCaptureSession已经准备完成,开始显示预览界面mCaptureSession = cameraCaptureSession;setCameraCaptureSession();}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {//配置失败}}, null);} catch (CameraAccessException e) {e.printStackTrace();}}/*** 设置CameraCaptureSession的特征:* <p>* 自动对焦,闪光灯*/private void setCameraCaptureSession() {try {setFocus(mPreviewRequestBuilder);//若是需要则开启,闪光灯setAutoFlash(mPreviewRequestBuilder);// 最后,开启相机预览界面的显示mPreviewRequest = mPreviewRequestBuilder.build();//为CameraCaptureSession设置复用的CaptureRequest。mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, workThreadManager.getBackgroundHandler());} catch (CameraAccessException e) {e.printStackTrace();}}private float currentZoom;@Overridepublic void notifyFocusState() {if (mPreviewRequestBuilder != null) {try {currentZoom = maxZoom * camera2Manager.getZoomProportion();setZoom(currentZoom);} catch (Exception e) {e.printStackTrace();}}}private Rect zoomRect;private void setZoom(float currentZoom) {try {zoomRect=createZoomReact();if (zoomRect==null){Log.i(TAG, "相机不支持 zoom " );return ;}mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION,zoomRect);mPreviewRequest = mPreviewRequestBuilder.build();Log.i(TAG, " 最大缩放值 " + maxZoom + " 设置缩放值 " + currentZoom );//为CameraCaptureSession设置复用的CaptureRequest。mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, workThreadManager.getBackgroundHandler());} catch (Exception e) {e.printStackTrace();}}/*** 计算出zoom所对应的裁剪区域* @return*/private Rect createZoomReact() {if (currentZoom==0){return null;}try {Rect rect = cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);if (rect==null){return null;}zoomRect =Camera2Utils.createZoomRect(rect,currentZoom);Log.i(TAG, "zoom对应的 rect对应的区域 " + zoomRect.left + " " + zoomRect.right + " " + zoomRect.top + " " + zoomRect.bottom);} catch (Exception e) {e.printStackTrace();}return zoomRect;}/*** -* 设置调焦方式:自动连续对焦还是手动调焦** @param requestBuilder*/private void setFocus(CaptureRequest.Builder requestBuilder) {
/*        if (camera2Manager.isManualFocus()) {float focusDistance = minimum_focus_distance * camera2Manager.getZoomProportion();setManualFocus(requestBuilder, focusDistance);} else {}*/setAutoFocus(requestBuilder);}/*** 设置连续自动对焦** @param requestBuilder*/private void setAutoFocus(CaptureRequest.Builder requestBuilder) {if (requestBuilder != null) {//为相机预览设置连续对焦。requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);}}/*** 设置手动对焦,设置焦距值** @param requestBuilder*/private void setManualFocus(CaptureRequest.Builder requestBuilder, float distance) {//若是机器不支持焦距设置,则需要检查。try {if (requestBuilder != null) {//先关闭自动对焦的模式requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_OFF);requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_OFF);Log.i(TAG, "手动调焦的 " + distance + " 最大范围值是 " + minimum_focus_distance);//设置焦距值requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, distance);}} catch (Exception e) {e.printStackTrace();}}/*** Camera state: Showing camera preview.* 相机预览状态*/private static final int STATE_PREVIEW = 0;/*** Camera state: Waiting for the focus to be locked.* <p>* 相机拍照,被锁住,等待焦点状态*/private static final int STATE_WAITING_LOCK = 1;/*** Camera state: Waiting for the exposure to be precapture state.*/private static final int STATE_WAITING_PRECAPTURE = 2;/*** Camera state: Waiting for the exposure state to be something other than precapture.*/private static final int STATE_WAITING_NON_PRECAPTURE = 3;/*** Camera state: Picture was taken.* 图片已经获取*/private static final int STATE_PICTURE_TAKEN = 4;/*** The current state of camera state for taking pictures.** @see #mCaptureCallback*/private int mState = STATE_PREVIEW;/*** CameraCaptureSession.CaptureCallback : 处理捕获到的JPEG事件。*/private CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() {private void process(CaptureResult result) {switch (mState) {//正常预览状态case STATE_PREVIEW: {break;}//刚开始拍照,锁住,等待状态case STATE_WAITING_LOCK: {//当前自动对焦的状态Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);if (afState == null) {captureStillPicture();} else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState || CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {// CONTROL_AE_STATE can be null on some devicesInteger aeState = result.get(CaptureResult.CONTROL_AE_STATE);if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {mState = STATE_PICTURE_TAKEN;captureStillPicture();} else {runPrecaptureSequence();}}else{mState = STATE_PICTURE_TAKEN;captureStillPicture();}break;}//等待,预捕获case STATE_WAITING_PRECAPTURE: {// CONTROL_AE_STATE can be null on some devicesInteger aeState = result.get(CaptureResult.CONTROL_AE_STATE);if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {mState = STATE_WAITING_NON_PRECAPTURE;}break;}//已经完成预捕获,直接拍照。case STATE_WAITING_NON_PRECAPTURE: {// CONTROL_AE_STATE can be null on some devicesInteger aeState = result.get(CaptureResult.CONTROL_AE_STATE);if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {mState = STATE_PICTURE_TAKEN;captureStillPicture();}break;}default:break;}}@Overridepublic void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {process(partialResult);}@Overridepublic void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {process(result);}};/*** <p>* 运行预捕获的序列,为捕获一个静态图片*/private void runPrecaptureSequence() {try {// 告诉相机,这里触发.mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);//设置成预捕获状态,将需等待。mState = STATE_WAITING_PRECAPTURE;mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, workThreadManager.getBackgroundHandler());} catch (CameraAccessException e) {e.printStackTrace();}}/*** 拍照一个静态的图片* ,当在CaptureCallback监听器响应的时候调用该方法。* <p>* 当数字调焦缩放的时候,在写入图片数中也要设置。*/private void captureStillPicture() {try {final Activity activity = getTextureViewContext();if (null == activity || null == mCameraDevice) {return;}// 创建一个拍照的CaptureRequest.Builderfinal CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);captureBuilder.addTarget(imageReader.getSurface());// 使用相同的AE和AF模式作为预览.captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);//让相机中缩放效果和图片保持一致//   zoomRect=createZoomReact();if (zoomRect != null) {Log.i(TAG," 拍照 添加裁剪区域 "+zoomRect.toString());captureBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoomRect);}setAutoFlash(captureBuilder);// 方向int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, Camera2Utils.getOrientation(ORIENTATIONS, mSensorOrientation, rotation));CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {@Overridepublic void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {//拍照完成,进行释放焦点操作。unlockFocus();}};//先停止以前的预览状态mCaptureSession.stopRepeating();mCaptureSession.abortCaptures();//执行拍照状态mCaptureSession.capture(captureBuilder.build(), captureCallback, null);} catch (CameraAccessException e) {e.printStackTrace();}}/*** 完成一些列拍照后,释放焦点。*/private void unlockFocus() {try {// 重置一系列的对焦mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);setAutoFlash(mPreviewRequestBuilder);mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, workThreadManager.getBackgroundHandler());// 恢复正常状态mState = STATE_PREVIEW;mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, workThreadManager.getBackgroundHandler());Log.i(TAG, TAG+" 拍照完成,释放焦点  unlockFocus() ");} catch (CameraAccessException e) {e.printStackTrace();}}/*** 设置是否开启闪光灯** @param requestBuilder*/private void setAutoFlash(CaptureRequest.Builder requestBuilder) {if (mFlashSupported) {requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);}}/*** 获取到最大的焦距值*/private float minimum_focus_distance;/*** 获取相机参数的类*/private CameraCharacteristics cameraCharacteristics;/*** 设置Camera的相关参数变量,长、宽,且返回相机的Id.** @param width* @param height*/private void setUpCameraOutputs(Activity activity, int width, int height) {CameraManager manager = (CameraManager) getTextureViewContext().getSystemService(Context.CAMERA_SERVICE);try {//获取到可用的相机for (String cameraId : manager.getCameraIdList()) {//获取到每个相机的参数对象,包含前后摄像头,分辨率等cameraCharacteristics = manager.getCameraCharacteristics(cameraId);if (!Camera2Utils.matchCameraDirection(cameraCharacteristics, currentDirection)) {continue;}//存储流配置类StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);if (map == null) {continue;}//检查设备,是否支持自动对焦mAutoFocusSupported = Camera2Utils.checkAutoFocus(cameraCharacteristics);//获取最小焦距值。Float minFocalDistance = Camera2Utils.getMinimumFocusDistance(cameraCharacteristics);if (minFocalDistance != null) {minimum_focus_distance = minFocalDistance;}Float maxZoomValue = Camera2Utils.getMaxZoom(cameraCharacteristics);if (maxZoomValue != null) {maxZoom = maxZoomValue;}Log.i(TAG, (currentDirection == CameraCharacteristics.LENS_FACING_BACK ? "后" : "前") + " 摄像头 " + " 是否支持自动对焦 " + mAutoFocusSupported + " 获取到焦距的最大值 " + minimum_focus_distance + " 最大的缩放值 " + maxZoom);//对于静态图片,使用可用的最大值来拍摄。Size largest = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new CompareSizeByArea());//设置ImageReader,将大小,图片格式imageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, /*maxImages*/2);imageReader.setOnImageAvailableListener(onImageAvailableListener, workThreadManager.getBackgroundHandler());//获取到屏幕的旋转角度,进一步判断是否,需要交换维度来获取预览大小int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation();//获取相机传感器方向mSensorOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);boolean swappedDimensions = false;switch (displayRotation) {case Surface.ROTATION_0:case Surface.ROTATION_180:if (mSensorOrientation == 90 || mSensorOrientation == 270) {swappedDimensions = true;}break;case Surface.ROTATION_90:case Surface.ROTATION_270:if (mSensorOrientation == 0 || mSensorOrientation == 180) {swappedDimensions = true;}break;default:Log.e(TAG, "Display rotation is invalid: " + displayRotation);}Point displaySize = new Point();activity.getWindowManager().getDefaultDisplay().getSize(displaySize);int rotatedPreviewWidth = width;int rotatedPreviewHeight = height;int maxPreviewWidth = displaySize.x;int maxPreviewHeight = displaySize.y;//当角度反了的时候if (swappedDimensions) {rotatedPreviewWidth = height;rotatedPreviewHeight = width;maxPreviewWidth = displaySize.y;maxPreviewHeight = displaySize.x;}if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {maxPreviewWidth = MAX_PREVIEW_WIDTH;}if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {maxPreviewHeight = MAX_PREVIEW_HEIGHT;}mPreviewSize = Camera2Utils.chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,maxPreviewHeight, largest, new CompareSizeByArea());// 计算出来的预览大小,设置成TextureView宽高.int orientation = activity.getResources().getConfiguration().orientation;AutoFitTextureView mTextureView = (AutoFitTextureView) getTextureView();//横屏if (orientation == Configuration.ORIENTATION_LANDSCAPE) {mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());} else {mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());}// 检查,相机是否支持闪光。Boolean available = cameraCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);mFlashSupported = available == null ? false : available;mCameraId = cameraId;// Log.i(TAG, " 根据相机的前后摄像头" + mCameraId + " 方向是:" + currentDirection);return;}} catch (Exception e) {e.printStackTrace();//不支持该设备if (e instanceof NullPointerException) {ToastUtils.showToast(appContext, "设备不支持Camera2 API");}}}}

录像操作类代码:

public class VideoRecordOperator extends BaseCamera2Operator {public static final String TAG = VideoRecordOperator.class.getSimpleName();private WorkThreadUtils workThreadManager;/*** 视频录制的大小*/private Size mVideoSize;/*** 相机预览的大小Size*/private Size mPreviewSize;/*** MediaRecorder*/private MediaRecorder mMediaRecorder;/*** 当前是否是在录制视频*/private boolean mIsRecordingVideo;/*** 传感器的方向*/private Integer mSensorOrientation;/*** 相机预览请求的Builder*/private CaptureRequest.Builder mPreviewBuilder;/*** 点击开启录制时候创建的新视频文件路径*/private String mNextVideoAbsolutePath;private CompositeSubscription compositeSubscription;private Camera2Manager camera2Manager;public VideoRecordOperator(Camera2Manager camera2Manager) {this.camera2Manager = camera2Manager;this.workThreadManager = camera2Manager.getWorkThreadManager();this.oldVideoPath = new CopyOnWriteArrayList<>();this.compositeSubscription = new CompositeSubscription();}@Overridepublic void writePictureData(Image image) {}/*** 开始相机的预览界面,创建一个预览的session会话。*/@Overridepublic void startPreView() {TextureView mTextureView = getTextureView();if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {return;}//开始相机预览try {closePreviewSession();SurfaceTexture texture = mTextureView.getSurfaceTexture();assert texture != null;texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);Surface previewSurface = new Surface(texture);mPreviewBuilder.addTarget(previewSurface);//从拍照切换到摄像头,获取录像完成后需要重新恢复以前的状态float currentZoom = camera2Manager.getZoomProportion() * maxZoom;updateZoomRect(currentZoom);mCameraDevice.createCaptureSession(Collections.singletonList(previewSurface),new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession session) {mPreviewSession = session;updatePreview();}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession session) {Activity activity = getTextureViewContext();if (null != activity) {Toast.makeText(activity, "相机预览配置失败", Toast.LENGTH_SHORT).show();}}}, workThreadManager.getBackgroundHandler());} catch (CameraAccessException e) {e.printStackTrace();}if (null != mTextureView) {configureTransform(getTextureViewContext(), mTextureView.getWidth(), mTextureView.getHeight());}}/*** 在 startPreView()之后执行用于更新相机预览界面*/private void updatePreview() {if (null == mCameraDevice) {return;}try {setUpCaptureRequestBuilder(mPreviewBuilder);mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, workThreadManager.getBackgroundHandler());} catch (CameraAccessException e) {e.printStackTrace();}}private void setUpCaptureRequestBuilder(CaptureRequest.Builder builder) {builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);}/*** 为相机创建一个CameraCaptureSession*/private CameraCaptureSession mPreviewSession;@Overridepublic void closePreviewSession() {if (mPreviewSession != null) {mPreviewSession.close();mPreviewSession = null;}}@Overridepublic void cameraClick() {if (mIsRecordingVideo) {stopRecordingVideo(true);} else {startRecordingVideo();}}/*** 停止录制*/private void stopRecordingVideo(final boolean isFinish) {// UImIsRecordingVideo = false;/*** 在MediaRecorder停止前,停止相机预览,防止抛出serious error异常。** android.hardware.camera2.CameraAccessException: The camera device has encountered a serious error** 解决方式:https://stackoverflow.com/questions/27907090/android-camera-2-api*/try {mPreviewSession.stopRepeating();mPreviewSession.abortCaptures();} catch (CameraAccessException e) {e.printStackTrace();}Subscription subscription = Observable//延迟三十毫秒.timer(30, TimeUnit.MICROSECONDS, Schedulers.computation()).observeOn(AndroidSchedulers.mainThread()).subscribe(l -> {// 停止录制mMediaRecorder.stop();mMediaRecorder.reset();if (isFinish) {isRecordGonging = false;Log.i(TAG, "stopRecordingVideo 录制完成");if (camera2VideoRecordCallBack != null) {camera2VideoRecordCallBack.finishRecord();}mergeMultipleFileCallBack();mNextVideoAbsolutePath = null;this.oldVideoPath.clear();} else {//暂停的操作Log.i(TAG, "pauseRecordingVideo 录制暂停");//若是开始新的录制,原本暂停产生的多个文件合并成一个文件。this.oldVideoPath.add(mNextVideoAbsolutePath);if (oldVideoPath.size() > 1) {mergeMultipleFile();}mNextVideoAbsolutePath = null;}startPreView();});this.compositeSubscription.add(subscription);}/*** 暂停后又从新恢复录制,合并多个视频文件*/private void mergeMultipleFile() {Log.i(TAG, " mergeMultipleFile  开始操作:文件个数 " + this.oldVideoPath.size());Subscription subscription = ObservableBuilder.createMergeMuiltFile(appContext, this.oldVideoPath.get(0), this.oldVideoPath.get(1)).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(filePath -> {this.oldVideoPath.clear();this.oldVideoPath.add(filePath);Log.i(TAG, " mergeMultipleFile  完成: 文件个数" + this.oldVideoPath.size());});this.compositeSubscription.add(subscription);}/*** 完成录制,输出最终的视频录制文件*/private void mergeMultipleFileCallBack() {if (this.oldVideoPath.size() > 0) {Log.i(TAG, " mergeMultipleFileCallBack 开始操作:文件个数 " + this.oldVideoPath.size());Subscription subscription = ObservableBuilder.createMergeMuiltFile(appContext, this.oldVideoPath.get(0), mNextVideoAbsolutePath).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(s -> {if (camera2ResultCallBack != null) {camera2ResultCallBack.callBack(ObservableBuilder.createVideo(s));}Log.i(TAG, " mergeMultipleFileCallBack 完成 且回调");ToastUtils.showToast(appContext, "视频文件保存在" + s);});this.compositeSubscription.add(subscription);} else {if (camera2ResultCallBack != null) {camera2ResultCallBack.callBack(ObservableBuilder.createVideo(mNextVideoAbsolutePath));}ToastUtils.showToast(appContext, "视频文件保存在" + mNextVideoAbsolutePath);}}/*** 暂停录制*/public void pauseRecordingVideo() {stopRecordingVideo(false);}/*** 开始视频录制,创建一个录像的session会话。*/private void startRecordingVideo() {Log.i(TAG, " startRecordingVideo  录制初始化 ");TextureView mTextureView = getTextureView();if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {return;}try {closePreviewSession();setUpMediaRecorder();SurfaceTexture texture = mTextureView.getSurfaceTexture();assert texture != null;texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());//创建录制的session会话mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);List<Surface> surfaces = new ArrayList<>();// 为相机预览设置SurfaceSurface previewSurface = new Surface(texture);surfaces.add(previewSurface);mPreviewBuilder.addTarget(previewSurface);// 为 MediaRecorder设置SurfaceSurface recorderSurface = mMediaRecorder.getSurface();surfaces.add(recorderSurface);mPreviewBuilder.addTarget(recorderSurface);//与未录像的状态保持一致。if (zoomRect != null) {mPreviewBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoomRect);}// Start a capture session// Once the session starts, we can update the UI and start recordingmCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {mPreviewSession = cameraCaptureSession;updatePreview();Log.i(TAG, " startRecordingVideo  正式开始录制 ");getTextureViewContext().runOnUiThread(() -> {mIsRecordingVideo = true;isRecordGonging = true;mMediaRecorder.start();if (camera2VideoRecordCallBack != null) {camera2VideoRecordCallBack.startRecord();}});}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {Activity activity = getTextureViewContext();if (null != activity) {Toast.makeText(activity.getApplicationContext(), "相机设备配置失败", Toast.LENGTH_SHORT).show();}}}, workThreadManager.getBackgroundHandler());} catch (CameraAccessException | IOException e) {e.printStackTrace();}}/*** 设置媒体录制器的配置参数* <p>* 音频,视频格式,文件路径,频率,编码格式等等** @throws IOException*/private void setUpMediaRecorder() throws IOException {final Activity activity = getTextureViewContext();if (null == activity) {return;}mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);mNextVideoAbsolutePath = FileUtils.createVideoDiskFile(appContext, FileUtils.createVideoFileName()).getAbsolutePath();mMediaRecorder.setOutputFile(mNextVideoAbsolutePath);mMediaRecorder.setVideoEncodingBitRate(10000000);//每秒30帧mMediaRecorder.setVideoFrameRate(30);mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();switch (mSensorOrientation) {case SENSOR_ORIENTATION_DEFAULT_DEGREES:mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation));break;case SENSOR_ORIENTATION_INVERSE_DEGREES:mMediaRecorder.setOrientationHint(ORIENTATIONS.get(rotation));break;default:break;}mMediaRecorder.prepare();}private List<String> oldVideoPath;@Overridepublic void startOperate() {TextureView textureView = getTextureView();if (textureView.isAvailable()) {openCamera(getTextureViewContext(), textureView.getWidth(), textureView.getHeight());} else {textureView.setSurfaceTextureListener(mSurfaceTextureListener);}}@Overridepublic void stopOperate() {try {mCameraOpenCloseLock.acquire();closePreviewSession();if (null != mCameraDevice) {mCameraDevice.close();mCameraDevice = null;}if (null != mMediaRecorder) {mMediaRecorder.release();mMediaRecorder = null;}} catch (InterruptedException e) {throw new RuntimeException("Interrupted while trying to lock camera closing.");} finally {mCameraOpenCloseLock.release();}}private boolean isRecordGonging = false;/*** 是否在进行视频录制,录制状态,包含进行中,暂停中。** @return*/public boolean isVideoRecord() {return isRecordGonging;}private Rect zoomRect;/*** 更新缩放,数字调焦** @param currentZoom*/private void updateZoomRect(float currentZoom) {try {Rect rect = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);if (rect == null) {return;}zoomRect = Camera2Utils.createZoomRect(rect, currentZoom);if (zoomRect == null) {return;}Log.i(TAG, "zoom对应的 rect对应的区域 " + zoomRect.left + " " + zoomRect.right + " " + zoomRect.top + " " + zoomRect.bottom);mPreviewBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoomRect);} catch (Exception e) {e.printStackTrace();}}private float maxZoom;private CameraCharacteristics characteristics;@Overrideprotected void openCamera(Activity activity, int width, int height) {if (PermissionsManager.checkVideoRecordPermission(getTextureViewContext())) {if (null == activity || activity.isFinishing()) {return;}AutoFitTextureView textureView = (AutoFitTextureView) getTextureView();CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);try {Log.d(TAG, "tryAcquire");if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {throw new RuntimeException("锁住相机开启,超时");}for (String cameraId : manager.getCameraIdList()) {characteristics = manager.getCameraCharacteristics(cameraId);if (!Camera2Utils.matchCameraDirection(characteristics, currentDirection)) {continue;}//存储流配置类StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);if (map == null) {continue;}Log.i(TAG, "视频录制,重新配置相机设备" + currentDirection + " " + cameraId);mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);Float maxZoomValue = Camera2Utils.getMaxZoom(characteristics);if (maxZoomValue != null) {maxZoom = maxZoomValue;}// 计算相机预览和视频录制的的SizemVideoSize = Camera2Utils.chooseVideoSize(map.getOutputSizes(MediaRecorder.class));mPreviewSize = Camera2Utils.chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height, mVideoSize);int orientation = activity.getResources().getConfiguration().orientation;if (orientation == Configuration.ORIENTATION_LANDSCAPE) {textureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());} else {textureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());}configureTransform(activity, width, height);mMediaRecorder = new MediaRecorder();manager.openCamera(cameraId, stateCallback, null);return;}} catch (CameraAccessException e) {ToastUtils.showToast(appContext, "不能访问相机");activity.finish();} catch (NullPointerException e) {ToastUtils.showToast(appContext, "当前设备不支持Camera2 API");} catch (InterruptedException e) {throw new RuntimeException("在锁住相机开启期间被打断.");}}}@Overrideprotected void configureTransform(Activity activity, int viewWidth, int viewHeight) {TextureView textureView = getTextureView();if (null == textureView || null == mPreviewSize || null == activity) {return;}int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();Matrix matrix = new Matrix();RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());float centerX = viewRect.centerX();float centerY = viewRect.centerY();if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);float scale = Math.max((float) viewHeight / mPreviewSize.getHeight(), (float) viewWidth / mPreviewSize.getWidth());matrix.postScale(scale, scale, centerX, centerY);matrix.postRotate(90 * (rotation - 2), centerX, centerY);}textureView.setTransform(matrix);}@Overridepublic void notifyFocusState() {float currentZoom = camera2Manager.getZoomProportion() * maxZoom;updateZoomRect(currentZoom);updatePreview();}private Camera2VideoRecordCallBack camera2VideoRecordCallBack;public void setCamera2VideoRecordCallBack(Camera2VideoRecordCallBack camera2VideoRecordCallBack) {this.camera2VideoRecordCallBack = camera2VideoRecordCallBack;}
}

Fragment中进行调用:

public class CameraFragment extends Fragment implements CameraContract.View<CameraContract.Presenter>, View.OnClickListener, RadioGroup.OnCheckedChangeListener, VerticalProgressBarLayout.VerticalMoveResultListener {public static final String TAG = CameraFragment.class.getSimpleName();private ImageView show_result_iv;private ImageView controller_state_iv;private VerticalProgressBarLayout verticalProgressBarLayout;public static CameraFragment newInstance() {CameraFragment cameraFragment = new CameraFragment();return cameraFragment;}private AutoFitTextureView textureView;private TextView show_record_tv, record_tip_circle;private View rootView;private CameraContract.Presenter presenter;private Animator flashAnimator;@Nullable@Overridepublic View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {this.rootView = inflater.inflate(R.layout.fragment_camera, container, false);initView();return this.rootView;}@Overridepublic void showToast(String content) {ToastUtils.showToastRunUIThread(getActivity(), content);}private void initView() {this.textureView = rootView.findViewById(R.id.camera_auto_fit_texture_view);this.show_result_iv = rootView.findViewById(R.id.camera_show);this.show_record_tv = rootView.findViewById(R.id.camera_video_record_tip_time_tv);this.record_tip_circle = rootView.findViewById(R.id.camera_video_record_tip_bg);this.rootView.findViewById(R.id.camera_btn).setOnClickListener(this);this.verticalProgressBarLayout = rootView.findViewById(R.id.camera_vertical_progress_bar);this.controller_state_iv = this.rootView.findViewById(R.id.camera_right_top_controller);this.controller_state_iv.setTag(CameraContract.View.MODE_RECORD_FINISH);this.controller_state_iv.setOnClickListener(this);this.show_result_iv.setOnClickListener(this);((RadioGroup) this.rootView.findViewById(R.id.camera_switch_radioGroup)).setOnCheckedChangeListener(this);((RadioGroup) this.rootView.findViewById(R.id.camera_direction_radioGroup)).setOnCheckedChangeListener(this);this.verticalProgressBarLayout.setVerticalMoveResultListener(this);}@Overridepublic void onResume() {super.onResume();if (presenter != null) {presenter.onResume();}}@Overridepublic void onPause() {super.onPause();if (presenter != null) {presenter.onPause();}}@Overridepublic void setPresenter(CameraContract.Presenter presenter) {this.presenter = presenter;}@Overridepublic TextureView getCameraView() {return textureView;}protected String filePath;@Overridepublic void loadPictureResult(String filePath) {this.filePath = filePath;GlideLoader.loadNetWorkResource(getActivity(), filePath, show_result_iv);}@Overridepublic void onClick(View v) {switch (v.getId()) {//点击按钮,进行拍照或录像case R.id.camera_btn:this.presenter.takePictureOrVideo();break;// 控制视频录制的开关,包含暂停,恢复录制case R.id.camera_right_top_controller:int mode = (int) controller_state_iv.getTag();//录制状态中,可以暂停if (mode == CameraContract.View.MODE_RECORD_START) {this.presenter.stopRecord();}//暂停状态,可以继续开始录制else if (mode == CameraContract.View.MODE_RECORD_STOP) {this.presenter.restartRecord();}break;//查看大图case R.id.camera_show:if (!TextUtils.isEmpty(filePath)) {PictureActivity.openActivity(getActivity(), filePath);}break;default:break;}}@Overridepublic void setTimingShow(String timing) {this.show_record_tv.setText(timing);}@Overridepublic void switchRecordMode(int mode) {switch (mode) {//录制开始case CameraContract.View.MODE_RECORD_START:this.show_record_tv.setVisibility(View.VISIBLE);this.record_tip_circle.setVisibility(View.VISIBLE);this.controller_state_iv.setImageResource(R.drawable.camera_stop_iv);if (flashAnimator != null && flashAnimator.isRunning()) {flashAnimator.cancel();}break;//录制暂停case CameraContract.View.MODE_RECORD_STOP:this.controller_state_iv.setImageResource(R.drawable.camera_start_iv);this.show_record_tv.setVisibility(View.INVISIBLE);flashAnimator = AnimatorBuilder.createFlashAnimator(this.record_tip_circle);flashAnimator.start();break;//录制完成case CameraContract.View.MODE_RECORD_FINISH:this.show_record_tv.setText("");this.show_record_tv.setVisibility(View.GONE);this.record_tip_circle.setVisibility(View.GONE);this.controller_state_iv.setImageResource(R.drawable.camera_init_iv);break;default:break;}this.controller_state_iv.setTag(mode);}@Overridepublic void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {switch (checkedId) {//切换成录像模式case R.id.camera_video_record_btn:this.presenter.switchMode(Constant.MODE_VIDEO_RECORD);break;//切换到拍照模式case R.id.camera_switch_picture_btn:this.presenter.switchMode(Constant.MODE_CAMERA);break;//切换到前摄像头case R.id.camera_direction_front:this.presenter.switchCamera(Camera2Manager.CAMERA_DIRECTION_FRONT);break;//切换到后摄像头case R.id.camera_direction_back:this.presenter.switchCamera(Camera2Manager.CAMERA_DIRECTION_BACK);break;default:break;}}@Overridepublic void moveDistance(float verticalBias) {Log.d(TAG, "moveDistance--verticalBias: " + verticalBias);if (presenter != null) {presenter.setManualFocus(verticalBias);}}
}

另外一个项目的效果图:

 源码地址:

github开源专业相机https://github.com/SereneGuest/Camera2

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

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

相关文章

天猫精灵Aligenie对接记录(三)

原文 https://www.jksxit.com/essay/42 服务端需要开发API接口处理阿里精灵发送的智能设备控制指令&#xff0c;并遵循AliGenie智能家居接入协议。 &#xff08;1&#xff09;同步模式

天猫精灵智能设备对接(4)

apache2php7.0 web服务器的构建 我个人使用的服务器是腾讯云&#xff0c;系统是ubuntu16.04&#xff0c;如果大家要是没有云服务器可以采用花生壳内网穿透的方法&#xff0c;还能得到一个只能在花生壳免费部署的域名&#xff0c;但是也要花6块钱&#xff0c;看需求了&#xff0…

天猫精灵智能家居对接,及天猫iot官网配置图文讲解(二)

天猫精灵智能家居对接,及天猫iot官网配置图文讲解&#xff08;二&#xff09; 2、天猫精灵设备对接 2-1、介绍 ​ 上一章里&#xff0c;我已经讲了天猫精灵的技能配置&#xff0c;设备创建&#xff0c;登录验证这三个部分做了&#xff0c;此次篇文章就讲之后的设备查询&…

天猫精灵智能设备对接(5) SSL https设置

至于什么是SSL证书具体有什么用&#xff0c;想了解的自行百度吧&#xff0c;再不行谷歌&#xff0c;百度出来的东西比我这个外行巴拉巴拉说半天来的痛快&#xff0c;本来腾讯云是送免费一年的SSL证书的&#xff0c;但是我赶得非常不巧&#xff0c;在我做测试那几天腾讯的ssl服务…

天猫精灵智能设备对接(8) 开发者网关地址

洋洋洒洒六七千字已经搭进去了&#xff0c;终于把服务器篇写的差不多了&#xff0c;当然小狂不是专业的写手&#xff0c;有些东西写的凑合看吧&#xff0c;只是说明过程&#xff0c;并不修饰言辞&#xff0c;看的舒服就点个赞&#xff0c;不舒服就当学东西了吧。这篇文章我们将…

springboot项目接入天猫精灵

springboot项目接入天猫精灵 最近工作需要使用到天猫精灵的语音功能&#xff0c;大体是通过呼叫对应的“调用词”实现携带参数&#xff0c;然后调用我项目中的接口&#xff0c;以实现对应的业务。所以在此简单的记录下使用过程 实际上&#xff1a;天猫精灵的官方文档记录的也很…

天猫精灵智能家居对接,及天猫iot官网配置图文讲解(一)

天猫智能家居对接 1-1、介绍 这篇文章主要是介绍&#xff0c;如何使用java对接天猫精灵智能家居提供的api。这么做的好处就是能让用户通过天猫精灵发送命令到我们的服务器&#xff0c;然后操控设备执行一系列的命令&#xff0c;当然这些功能呢都是天猫精灵官方制定的协议&…

java对接天猫精灵语音助手实现对公司其下的智能设备进行控制(附上源码)

java对接天猫精灵语音助手实现对公司其下的智能设备进行控制 前言当初刚来广州 公司上一任java已经离职半年 &#xff0c;项目已经跑不动了&#xff0c;才招人的&#xff0c;所以我获得的是一个连跑都跑不起来的项目源码并且对项目一无所知&#xff0c;一年前网上并没有对接天…

天猫精灵智能设备对接(7) OAuth2.0

在开始之前先放两篇参考&#xff0c;一篇英文http://bshaffer.github.io/oauth2-server-php-docs/cookbook/&#xff0c;一篇中文https://www.cnblogs.com/endv/p/7868549.html&#xff0c;中文博客里的内容基本上是把英文官方文档翻译一遍。到这里如果你不知道OAuth2.0是啥可以…

天猫精灵对接2(OAuth 搭建)

根据 接入方式及流程 中的说明&#xff0c;可知&#xff0c;搭建过程中&#xff0c;我们需要自己整一个 OAuth 的授权平台&#xff0c;具体说明可以参考蟋蟀大哥的文章 ASP.NET WebApi OWIN 实现 OAuth 2.0 &#xff0c;我的实际代码也是基于文章给出的源码修改的。 第一步 认…

天猫精灵对接智能设备

why to do&#xff1a;   我之前一直很喜欢智能家居&#xff0c;可惜的是现在市场上成品的智能家居实在是太贵了&#xff0c;屌丝的码农是在背不起每月高额的房贷和装修费用的基础上&#xff0c;再买成品的智能设备&#xff08;像某米那样一个智能开关&#xff0c;竟然卖那么…

从零玩转系列之SpringBoot3-核心原理

一、简介 1.前置知识 ● Java17 ● Spring、SpringMVC、MyBatis ● Maven、IDEA 2.环境要求 环境&工具版本(or later)SpringBoot3.1.xIDEA2023.xJava17Maven3.5Tomcat10.0Servlet5.0GraalVM Community22.3Native Build Tools0.9.19 二、SpringBoot3-核心原理 1.事件和监听器…

SpringBoot3【⑤ 核心原理】

1. 事件和监听器 1. 生命周期监听 场景&#xff1a;监听应用的生命周期 1. 监听器-SpringApplicationRunListener 自定义SpringApplicationRunListener来监听事件&#xff1b; 1.1. 编写SpringApplicationRunListener 这个接口的实现类 1.2. 在 META-INF/spring.factories …

开发必备,开源 or 免费的 AI 编程助手

AI 大模型的火热&#xff0c;让开发圈近来如虎添翼&#xff0c;各种各样基于 AI 技术的开发者工具和新范式不断涌现&#xff0c;尤其是 Github 和 OpenAI 共同推出的 Copilot X &#xff0c;更是一骑绝尘。本文推荐一些开源 or 免费的 AI 编程工具&#xff0c;不妨试着用起来。…

超过5000人的2年研究表明,这一活动破坏你的身心健康

Tips 原文作者&#xff1a;Minda Zetlin 原文出处&#xff1a;A 2-Year Study of More Than 5,000 People Shows This 1 Activity Destroys Your Emotional and Physical Health 阅读时&#xff0c;把文中的 Fackbook 换成微信。 国外主要用 Facebook&#xff1b; 国内主要是微…

申请阿里云服务器并搭建公网可支持数据上传下载的HTTP服务器

1. 前言 拥有一台自己的云服务器可以做很多事情。阿里云服务器毫无疑问是国内最好的。 阿里云服务器可以用于各种互联网应用的搭建和运行&#xff0c;提供稳定、高性能的服务。 阿里云服务器的用途&#xff0c;包括但不限于以下几个方面&#xff1a; 网站托管&#xff1a;可以将…

谷歌眼镜秀出时尚风采:对面的女孩看过来

摘要&#xff1a;在近日举办的纽约时尚周上&#xff0c;让身材火辣的模特带上谷歌的眼镜&#xff0c;行走在T台之上。主打时尚牌&#xff0c;进一步加固谷歌眼镜在大众消费阶层的印象&#xff0c;尤其是女性消费者。谷歌眼镜创始人Sebastian Thrun指出&#xff1a;谷歌眼镜特别…

学生台灯什么牌子好对眼睛好?专业护眼灯的学生台灯分享

据报告统计&#xff0c;2022年我国儿童青少年总体近视率为52.7%&#xff0c;其中6岁儿童为14.3%&#xff0c;小学生为35.6%&#xff0c;初中生为71.1%&#xff0c;高中生为80.5%&#xff0c;这些数据让人不寒而栗&#xff01; 专家表示&#xff0c;导致儿童青少年近视的因素&am…

【UGP VR眼镜排行榜】2018VR眼镜眼镜哪个好?什么VR眼镜值得买?综合推荐十大热品

科技的发展&#xff0c;高科技产品层出不穷&#xff0c;VR眼镜的出现使人们足不出户也能享受到高品质的观影感受。VR(Virtual Reality&#xff09;即虚拟现实&#xff0c;简称VR.虚拟现实头戴显示器设备&#xff0c;简称VR头显VR眼镜.现在&#xff0c;VR眼镜已不是什么稀奇的东…

《谷歌眼镜》新书作者:眼镜需要成为AR的载体吗?

近10年前&#xff0c;谷歌推出了首款AR眼镜Google Glass&#xff0c;尽管这款产品并没有如预期般取得成功&#xff0c;但它为后续AR硬件技术的发展奠定了基础。我们知道&#xff0c;从微软HoloLens开始&#xff0c;AR头显/眼镜产品更侧重于B端应用&#xff0c;面向C端发售的很少…