本文主要是介绍Camera2学习笔记,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Camera2学习笔记
Camera API2 使用流程图
Camera2主要API
CameraManager 相机的管理类通过Context.getSystemService获取
1. getCameraCharacteristrics 查询Camera有哪些功能,这个对象是不可改变的
2. getCameraExtensionCharacteristics 查询某个Camera的Extension能力
3. getConcurrentCameraIds 获取可以同时配置会话(Session)的Camera的ID
4. getCameraIdList 获取当前可用的Camera的ID
5. openCamera 打开指定ID的Camera
6. registerAvailabilityCallback 注册一个监听回调来监听Camera的状态,是否可用
7. unregisterAvailabilityCallback 注销监听回调
CameraCharacteristics 记录了相机的详细信息,比如支持的图片格式,支持聚焦功能等
1. get(Key<T> key) 根据Key获取Value值
2. getKeys() 获取所有CameraCharacteristics支持的Key集合
3. getAvailableCaptureRequestKes() 获取所有CaptureRequest支持的Key集合
4. getAvailableCaptureResultKeys() 获取所有CaptureResult支持的Key集合
5. getPhysicalCameraIds() 获取当前逻辑Camera对应的物理Camera集合(我们手机后面实实在在的摄像头就是物理Camera,我们把任意几个物理Camera组合叫做逻辑Camera)
CameraDevice 是已打开的Camera设备
1. close 以最快的速度关闭Camera
2. getId 获取当前Camera对应的ID
3. createCaptureRequest(int templateType) 根据templateType创建CaptureRequest.Builder
4. createCaptureSession(SessionConfiguration config) 根据config创建CameraCaptureSession
5. setCameraAudioRestriction(int mode) 设置音频限制模式,比如屏蔽notifications的震动和声音
6. getCameraAudioRestriction 获取当前使用的音频限制模式
CameraDevice.StateCallback 一个接收相机更新状态的回调
这些状态更新包括有关设备完成启动,设备断开、关闭、设备错误的通知
1. ERROR_CAMERA_DEVICE 表示摄像头设备遇到了致命错误
2. ERROR_CAMERA_DISABLED 表示由于设备策略而无法打开摄像头设备
3. ERROR_CAMERA_IN_USE 表示摄像头设备已经在使用中
4. ERROR_CAMERA_SERVICE 表示摄像头服务遇到了致命错误
5. ERROR_MAX_CAMERAS_IN_USE 因为打开了太多摄像头设备导致无法打开该设备
CameraCaptureSession 这是一个相机会话,与Camera设备建立通道,后面对Camera设备的控制都是通过这个通道
通过 CameraDevice 类的 createCaptureSession() 方法创建,并在回调的 onConfigured(CameraCaptureSession session) 方法中获取实例1. capture 发送一个请求到Camera底层,参数Handler指定回调线程
2. captureSingleRequest 发送一个请求到C底层,参数Executor指定回调线程
3. captureBurst 发送一组请求到Camera底层,参数Handler指定回调线程
4. captureBurstRequests 发送一组请求到Camera底层,参数Executor指定回调线程
5. setRepeatingRequest 发送一组重复请求到Camera底层,参数Handler指定回调线程
6. setSingleRepeatingRequest 发送一组重复请求到Camera底层,参数Executor指定回调线程
7. setRepeatingBurst 发送一组重复请求到Camera底层,参数Handler指定回调线程
8. setRepeatingBurstRequest 发送一组重复请求到Camera底层,参数Executo指定回调线程
9. stopRepeating 通知相机服务不要向HAL发送请求了
10. abortCaptures 通知相机服务不要向HAL发送请求了,并且让HAL放弃还没有处理完的请
11. supportsOfflineProcessing(Surface surface) 判断surface是否支持离线处理模式
12. switchToOffline 将会话切换到离线处理模式
13. isReprocessable 判断是否支持重新处理
CameraCaptureSession.StateCallback 当相机捕获图像的状态发生变化时,会回调这个方法
其中只有 onConfigured 和 onConfigureFailed 两个方法是抽象的(必须重写),其余均有空实现1. onConfigureFailed(CameraCaptureSession session) 会话无法按照相应的配置发起请求时回调
2. onConfigured(CameraCaptureSession session) 相机设备完成配置并开始处理捕捉请求时回调
3. onActive(CameraCaptureSession session) 在 onConfigured 后执行,即开始真的处理捕捉请求了
4. onCaptureQueueEmpty(CameraCaptureSession session) 当相机设备的捕捉队列为空时回调
5. onClosed(CameraCaptureSession session) 当事务关闭时回调
6. onReady(CameraCaptureSession session) 每当没有捕捉请求处理时都会回调该方法,该方法会回调多次
CameraCaptureSession.CaptureCallback 当一个捕捉请求发送给相机设备时,这个方法会监听图像捕捉的进度
所有方法均有默认的空实现,根据需求重写相应的方法1. onCaptureStarted 当相机设备开始为请求捕捉并输出图像时回调
2. onCaptureProgressed 当图像捕获部分进行时就会回调该方法,此时一些(但不是全部)结果是可用的
3. onCaptureCompleted 当图像捕获完成时,并且结果可用时回调
4. onCaptureFailed 对应 onCaptureCompleted 方法,当相机设备产生 TotalCaptureResult 失败时就回调
5. onCaptureBufferLost 当捕获的数据没有被送到目标 surface 时被回调
CaptureRequest 这是一个图像请求
1. getKeys 获取当前CaptureRequest包含的Key集合
2. getTag 获取CaptureRequest的Tag,每个CaptureRequest都有一个Tag,代表一个标识
3. get 获取Key对应的值
4. isReprocess 判断是否是一个再处理的请求(CaptureRequest)
5. CaptureResult 这是一个请求最终生成的图象信息
6. getKeys 获取当前CaptureReult所包含的Key集合
7. get 获取当前Key对应的值
8. getRequest 获取当前CaptureReeult对应的CaptureRequest
9. getCameraId 获取Camera的ID
CaptureResult 这是捕获单个图像的结果集
捕获结果是由摄像头在对捕获请求进行处理完成后产生,CaptureResult 对象是不可变的,常使用的子类是 TotalCaptureResult
我们在 CameraCaptureSession.CaptureCallback 类的回调方法 onCaptureProgressed() 和 onCaptureCompleted() 中都可以拿到 CaptureResult 的对象1. get(Key key) 获取 CaptureResult 中指定 key 的值
2. getFrameNumber() 获取该结果申请的帧的数量。
3. getKeys() 返回所有 Key 的列表。
4. getRequest() 返回这个结果对应的 CaptureRequest 对象。
Camera2基本流程
- 创建TextureView布局
- 在activity中获取TextureView对象并设置监听
- 在onSurfaceTextureAvailable中开始进行摄像头操作
- 创建子线程,用于监听和处理数据
- 通过context.getSystemService(Context.CAMERA_SERVICE)获取CameraManager对象
- 通过manager.getCameraCharacteristics(“0”)获取后置摄像头配置信息类,一般前置为 “1” 后置为 “0”
- 调用CameraManager.openCamera()方法在回调中获取CameraDevice对象
- 通过CameraDevice.createCaptureSession()方法在回调中获取CameraCaptureSession对象
- 构建CaptureRequest请求, 其中参数有三种模式分别是
- 预览 TEMPLATE_PREVIEW
- 拍照 TEMPLATE_STILL_CAPTURE
- 录像 TEMPLATE_RECORD
- 接着是通过CameraCaptureSession发送CaptureRequest, capture是只发一次请求, setRepeatingRequest则是不断发送请求.
- 摄像头捕获的数据即拍照数据可以在ImageReader.OnImageAvailableListener回调中获取,保存图片就是在这里开一个子线程进行保存, CaptureCallback中可以获取拍照实际的参数和当前Camera的状态.
Camera2实践
CameraDemo的总流程图
拍照流程
摄像头切换流程
实现思路:这里只是简单的前后摄像头的切换,首先通过 cameraManager.getCameraIdList() 获取当前设备摄像头ID列表,然后遍历该列表,通过当前Camera的ID来判断当前摄像头是前置还是后置,如果是前置则查询到后置摄像头ID并将其赋值为当前Camera的ID,反之亦然,拿到要切换摄像头ID后就是去调用 manager.openCamera 方法打开指定ID的摄像头,再调用此方法之前要把当前摄像头关闭即调用closeCamera,再调用 reopenCamera,主要实现代码如下
//切换摄像头
public void switchCamera() {//获取摄像头的管理者CameraManager cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);try {for (String id : cameraManager.getCameraIdList()) {CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);if (mCameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_BACK)) && characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_FRONT);closeCamera();reopenCamera();break;} else if (mCameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_FRONT)) && characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK) {mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_BACK);closeCamera();reopenCamera();break;}}} catch (Exception e) {e.printStackTrace();}
}//关闭摄像头,释放相关资源,这里采用了锁防止出现线程并发的问题
private void closeCamera() {try {mCameraOpenCloseLock.acquire();synchronized (mCameraStateLock) {mPendingUserCaptures = 0;mState = STATE_CLOSED;if (null != mCaptureSession) {mCaptureSession.close();mCaptureSession = null;}if (null != mCameraDevice) {mCameraDevice.close();mCameraDevice = null;}if (null != mJpegImageReader) {mJpegImageReader.close();mJpegImageReader = null;}}} catch (InterruptedException e) {e.printStackTrace();} finally {mCameraOpenCloseLock.release();}
}//重新打开摄像头
public void reopenCamera() {if (mTextureView.isAvailable()) {openCamera();} else {mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);}
}
拍照比例流程
实现思路:我的目标是实现 4:3、16:9、全屏这三种拍照比例,所以我通过 Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)) 来获取当前摄像头支持的Size列表,当然这只是一种方式,我是拍照所以选择JPEG格式,然后遍历该列表找出最接近4:3、16:9、全屏这三种拍照比例的Size并把它们存到sizeList中,其中全屏我是获取手机屏幕宽高比来进行相应的匹配的,最后就是当我按切换拍照比例的按钮时进行循环切换拍照比例,我其中configureTransform是当我们选择相应的拍照比例时,TextureView宽高比进行自适应,当然这样只是预览比例变化了,拍出来的图片还是之前的比例,所以我们还需要进行 closeCamera 和 reopenCamera,主要实现代码如下
//初始化拍照比例
private void initPictureSize() {CameraManager manager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);try {CameraCharacteristics characteristics= manager.getCameraCharacteristics(mCameraId);StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);List<Size> outputSize = Arrays.asList(map.getOutputSizes(ImageFormat.JPEG));//寻找一个最合适的尺寸与TextrueView适配//定义List用来存全屏、16:9、4:3的尺寸sizeList = new ArrayList<>();//全屏int mWidth = outputSize.get(0).getWidth();int mHeight = outputSize.get(0).getHeight();//16:9int mWidtha = outputSize.get(0).getWidth();int mHeighta = outputSize.get(0).getHeight();//4:3int mWidthb = outputSize.get(0).getWidth();int mHeightb = outputSize.get(0).getHeight();//TODO 如何计算各个拍照比例,需要把怎么确定index 0是4:3比例,2是16:9, 3是全屏的计算过程代码加入进来//获取手机屏幕宽高//方法三:应用程序显示区域指定可能包含应用程序窗口的显示部分,不包括系统装饰DisplayMetrics displayMetrics = getResources().getDisplayMetrics();int screenHeight = displayMetrics.widthPixels;int screenWidth = displayMetrics.heightPixels;Log.i(TAG, "updateCameraPreview: ---" + screenWidth);Log.i(TAG, "updateCameraPreview: ---" + screenHeight);//寻找与手机屏幕宽高最接近的预览尺寸double screenRatioa = (double) screenWidth / (double) screenHeight;//寻找最接近手机屏幕尺寸的预览尺寸double screenRatiob = Math.abs(screenRatioa - (double) outputSize.get(0).getWidth() / (double) outputSize.get(0).getHeight());//寻找最接近16:9的预览尺寸double ratioa = Math.abs(16.0 / 9.0 - (double) outputSize.get(0).getWidth() / (double) outputSize.get(0).getHeight());//寻找最接近4:3的预览尺寸double ratiob = Math.abs(4.0 / 3.0 - (double) outputSize.get(0).getWidth() / (double) outputSize.get(0).getHeight());//遍历尺寸for (int i = 1; i < outputSize.size() - 1; i++) {int w = outputSize.get(i).getWidth();int h = outputSize.get(i).getHeight();Log.i(TAG, "updateCameraPreview: w: " + w + " h:" + h);//全屏double ratios = Math.abs(screenRatioa - (double) w / (double) h);if (ratios < screenRatiob) {screenRatiob = ratios;mWidth = w;mHeight = h;}//16:9double ratio = Math.abs(16.0 / 9.0 - (double) w / (double) h);if (ratio < ratioa) {ratioa = ratio;mWidtha = w;mHeighta = h;}//4:3double ratiox = Math.abs(4.0 / 3.0 - (double) w / (double) h);if (ratiox < ratiob) {ratiob = ratiox;mWidthb = w;mHeightb = h;}}Log.i(TAG, "updateCameraPreview: 4:3 " + mWidthb + " " + mHeightb);Log.i(TAG, "updateCameraPreview: 16:9 " + mWidtha + " " + mHeighta);Log.i(TAG, "updateCameraPreview: 全屏 " + mWidth + " " + mHeight);sizeList.add(new Size(mWidthb, mHeightb));//4:3sizeList.add(new Size(mWidtha, mHeighta));//16:9sizeList.add(new Size(mWidth, mHeight));//全屏mCurrentPictureSize.setText("4:3"); //默认拍照比例largestJpeg = sizeList.get(0);} catch (CameraAccessException e) {e.printStackTrace();}
}//切换拍照比例
private void switchPictureSize() {isSwitchPictureSize = false;if (sizeIndex < sizeList.size() - 1) {sizeIndex++;} else {sizeIndex = 0;}switch (sizeIndex) {case 0:largestJpeg = sizeList.get(0);mCurrentPictureSize.setText("4:3");break;case 1:largestJpeg = sizeList.get(1);mCurrentPictureSize.setText("16:9");break;case 2:largestJpeg = sizeList.get(2);mCurrentPictureSize.setText("全屏");break;}configureTransform(mTextureView.getWidth(), mTextureView.getHeight());closeCamera();reopenCamera();
}
闪光模式切换流程
实现思路:
闪光模式一般有四个状态分别是:关闭、拍照打闪、长亮、自动,这里我只实现了关闭、拍照打闪、自动这三种状态,实现如下
关闭:
FLASH_MODE:FLASH_MODE_OFF
CONTROL_AE_MODE:CONTROL_AE_MODE_ON/CONTROL_AE_MODE_OFF
拍照打闪:
FLASH_MODE:FLASH_MODE_SINGLE (不管AE的结果,拍照时Flash都会打闪,这种模式下拍照前一定要通过 android.control.aePrecaptureTrigger进行预测光,不然拍下来的图可能会过曝或者全黑)
CONTROL_AE_MODE:CONTROL_AE_MODE_ON/CONTROL_AE_MODE_OFF
或者
FLASH_MODE:任意
CONTROL_AE_MODE:CONTROL_AE_MODE_ON_ALWAYS_FLASH
长亮:
FLASH_MODE:FLASH_MODE_TORCH
CONTROL_AE_MODE:CONTROL_AE_MODE_ON/CONTROL_AE_MODE_OFF
自动:
FLASH_MODE:任意
CONTROL_AE_MODE:CONTROL_AE_MODE_ON_AUTO_FLASH/CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE
通过我的CameraDemo流程图可以看出,闪光模式切换流程是
switchFlashMode -> CameraCaptureSession.CaptureCallback(mPreCatureCallback) -> process(打闪处理) -> captureStillPictureLcked(对图像再次处理) -> setUp3AControlsLocked -> CameraCaptureSession.CaptureCallback(mCatureCallback) ->handleCompletionLocked(开启异步线程池进行保存图像)
拍照之前要调用 setFlashMode,其中setUp3AControlsLocked方法是进行当前闪光模式判断然后进行相应调整
//切换闪光模式
private void switchFlashMode() {switch (mFlashMode) {case 0:mFlashMode = 1;mFlashBtn.setImageResource(R.drawable.ic_flash_auto);mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);try {mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(),mPreCaptureCallback, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();return;}break;case 1:mFlashMode = 2;mFlashBtn.setImageResource(R.drawable.ic_flash_open);mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);try {mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(),mPreCaptureCallback, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();return;}break;case 2:mFlashMode = 0;mFlashBtn.setImageResource(R.drawable.ic_flash_close);mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);try {mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(),mPreCaptureCallback, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();return;}break;}
}//设置闪光模式参数
private void setFlashMode() {switch (mFlashMode) {case 0:mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);break;case 1:mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);break;case 2:mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);break;}
}
这篇关于Camera2学习笔记的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!