一、应用背景

小白公司业务为车辆安全驾驶检测等,需要开启相机将图像传递给算法进行处理以达到行车预警的功能。由于项目需求,需要将开启相机这一块由原来的Camera1.0更新为Camera2.0,以下是小白开发中所遇到的问题与解决方案,仅供参考。

二、Camera2.0学习

1、Google官方demo

官方demo地址:android-Camera2Basic

2、流程


这里引用了管道的概念将安卓设备和摄像头之间联通起来,系统向摄像头发送 Capture 请求,而摄像头会返回 CameraMetadata。这一切建立在一个叫作 CameraCaptureSession 的会话中。

三、实践

1、申请权限

<uses-permission android:name="android.permission.CAMERA" />

Android 6.0以上需要进行权限的动态申请。

2、Camera2.0工具类

public class Camera2Proxy {private static final String TAG = "Camera2Proxy";private Activity mActivity;private int mCameraId = CameraCharacteristics.LENS_FACING_FRONT; // 要打开的摄像头IDprivate Size mPreviewSize; // 预览大小private CameraManager mCameraManager; // 相机管理者private CameraCharacteristics mCameraCharacteristics; // 相机属性private CameraDevice mCameraDevice; // 相机对象private CameraCaptureSession mCaptureSession;private CaptureRequest.Builder mPreviewRequestBuilder; // 相机预览请求的构造器private Semaphore mCameraOpenCloseLock = new Semaphore(1);//一个信号量以防止应用程序在关闭相机之前退出。private CaptureRequest mPreviewRequest;private Handler mBackgroundHandler;private HandlerThread mBackgroundThread;private ImageReader mImageReader;private Surface mPreviewSurface;private SurfaceTexture mPreviewSurfaceTexture;private OrientationEventListener mOrientationEventListener;private int mDisplayRotate = 0;private int mDeviceOrientation = 0; // 设备方向,由相机传感器获取private int mZoom = 1; // 缩放public static int CameraFPS = 0; // 帧率private int frameCount = 0;private long frameTime = SystemClock.elapsedRealtime();public long mLastPreviewFrameTime = SystemClock.elapsedRealtime(); // 最后一帧数据的时间private byte[] data = null;private boolean mStop = true;private final Object lock = new Object();private YuvCroper yuvCroper;private int cropH = 0;  //剪裁图像后的高度private DataSendThread mDataSendThread;private MediaRecorder mediaRecorder;private long startRecordTime = 0;// 录像开启时间private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90;private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270;public static final int MEDIA_QUALITY_HIGH = 30 * 100000;private int rotation;private boolean isRecording = false;private String filePath;private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray();private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray();static {DEFAULT_ORIENTATIONS.append(Surface.ROTATION_0, 90);DEFAULT_ORIENTATIONS.append(Surface.ROTATION_90, 0);DEFAULT_ORIENTATIONS.append(Surface.ROTATION_180, 270);DEFAULT_ORIENTATIONS.append(Surface.ROTATION_270, 180);}static {INVERSE_ORIENTATIONS.append(Surface.ROTATION_0, 270);INVERSE_ORIENTATIONS.append(Surface.ROTATION_90, 180);INVERSE_ORIENTATIONS.append(Surface.ROTATION_180, 90);INVERSE_ORIENTATIONS.append(Surface.ROTATION_270, 0);}/*** 打开摄像头的回调*/private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(@NonNull CameraDevice camera) {Log.d(TAG, "onOpened");mCameraOpenCloseLock.release();mCameraDevice = camera;int recordState = (int) SPUtils.get(mActivity, SPUtils.SP_RECORD_STATE, 0);if (recordState == 1) {initRecordPreviewRequest();} else {initPreviewRequest();}}@Overridepublic void onDisconnected(@NonNull CameraDevice camera) {Log.d(TAG, "onDisconnected");mCameraOpenCloseLock.release();releaseCamera();}@Overridepublic void onError(@NonNull CameraDevice camera, int error) {Log.e(TAG, "Camera Open failed, error: " + error);mCameraOpenCloseLock.release();releaseCamera();}};/*** 图像回调处理*/private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader reader) {Image image = reader.acquireLatestImage();if (image != null) {if (!mStop) {synchronized (lock) {data = ImageUtils.getBytesFromImageAsType(image, ImageUtils.YV12);}}image.close();}frameCount++;mLastPreviewFrameTime = SystemClock.elapsedRealtime();//记录帧率if (mLastPreviewFrameTime - frameTime >= 1000) {CameraFPS = frameCount;Log.e(TAG, "fps = " + CameraFPS);frameCount = 0;frameTime = mLastPreviewFrameTime;}}};@TargetApi(Build.VERSION_CODES.M)public Camera2Proxy(Activity activity) {mActivity = activity;mCameraManager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);try {//按照16:9的比例剪裁,剪裁后的图像尺寸不能是奇数,处理各个数据为4的倍数RectF rectF = new RectF(0, ((16.0f * MediaInfo.PRIVIEW_WIDTH - 9.0f * MediaInfo.PRIVIEW_HEIGHT) / (32.0f * MediaInfo.PRIVIEW_WIDTH)), 1, ((16.0f * MediaInfo.PRIVIEW_WIDTH + 9.0f * MediaInfo.PRIVIEW_HEIGHT) / (32.0f * MediaInfo.PRIVIEW_WIDTH)));Log.e("YuvCroper", "MediaInfo.PRIVIEW_WIDTH = " + MediaInfo.PRIVIEW_WIDTH + " MediaInfo.PRIVIEW_HEIGHT=" + MediaInfo.PRIVIEW_HEIGHT + " rectF=" + rectF.toString());yuvCroper = new YuvCroper(YuvCroper.YUV_420P, MediaInfo.PRIVIEW_HEIGHT, MediaInfo.PRIVIEW_WIDTH, rectF);} catch (Exception e) {e.printStackTrace();}mOrientationEventListener = new OrientationEventListener(mActivity) {@Overridepublic void onOrientationChanged(int orientation) {mDeviceOrientation = orientation;}};}@SuppressLint("MissingPermission")public void openCamera(int width, int height) {Log.v(TAG, "openCamera");startBackgroundThread(); // 对应 releaseCamera() 方法中的 stopBackgroundThread()mOrientationEventListener.enable();try {if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {throw new RuntimeException("Time out waiting to lock camera opening.");}try {mCameraCharacteristics = mCameraManager.getCameraCharacteristics(Integer.toString(0));StreamConfigurationMap map = mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);// 拍照大小,选择能支持的一个最大的图片大小mImageReader = ImageReader.newInstance(MediaInfo.PRIVIEW_WIDTH, MediaInfo.PRIVIEW_HEIGHT, ImageFormat.YUV_420_888, 2);mImageReader.setOnImageAvailableListener(   // 设置监听和后台线程处理器mOnImageAvailableListener, null);// 预览大小,根据上面选择的拍照图片的长宽比,选择一个和控件长宽差不多的大小mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height, MediaInfo.PRIVIEW_WIDTH, MediaInfo.PRIVIEW_HEIGHT);Log.d(TAG, "preview size: " + mPreviewSize.getWidth() + "*" + mPreviewSize.getHeight());// 打开摄像头mCameraManager.openCamera(Integer.toString(0), mStateCallback, mBackgroundHandler);startSendDataThread();} catch (Exception e) {Log.e(TAG,"No current camera was found!");e.printStackTrace();}} catch (Exception e) {e.printStackTrace();}}/*** 关闭摄像头*/public void releaseCamera() {Log.v(TAG, "releaseCamera");try {if (isRecording) {isRecording = false;if (mediaRecorder != null) {mediaRecorder.stop();mediaRecorder.reset();mediaRecorder.release();if (null != SDCardUtils.getInsSDCardPath()) {long end = System.currentTimeMillis();long duration = end - startRecordTime;SQLiteFactory.getInstance().getFileDao().insertFileEntity(startRecordTime, end, 3, (int) duration, 0);}}/** 生成录像对应缩略图*/if (null != filePath && null != SDCardUtils.getInsSDCardPath()) {ImageUtils.saveThumbnail2(filePath);filePath = null;}}if (null != mCaptureSession) {mCaptureSession.close();mCaptureSession = null;}if (mCameraDevice != null) {mCameraDevice.close();mCameraDevice = null;}if (mImageReader != null) {mImageReader.close();mImageReader = null;}mOrientationEventListener.disable();stopBackgroundThread(); // 对应 openCamera() 方法中的 startBackgroundThread()} catch (Exception e) {e.printStackTrace();}}/*** 设置预览的surface* @param surfaceTexture*/public void setPreviewSurface(SurfaceTexture surfaceTexture) {mPreviewSurfaceTexture = surfaceTexture;}/*** 预览初始化*/private void initPreviewRequest() {try {mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);if (mPreviewSurfaceTexture != null && mPreviewSurface == null) { // use texture viewmPreviewSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());mPreviewSurface = new Surface(mPreviewSurfaceTexture);}final Range<Integer>[] fpsRanges = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);Log.e(TAG, "SYNC_MAX_LATENCY_PER_FRAME_CONTROL: " + Arrays.toString(fpsRanges));mPreviewRequestBuilder.addTarget(mPreviewSurface); // 设置预览输出的 SurfacemPreviewRequestBuilder.addTarget(mImageReader.getSurface());mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface, mImageReader.getSurface()),new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession session) {mCaptureSession = session;mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRanges[fpsRanges.length - 1]);// 设置连续自动对焦mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AE_ANTIBANDING_MODE_AUTO);
//                            // 设置自动曝光
//                            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest
//                                    .CONTROL_AE_MODE_ON_AUTO_FLASH);// 设置完后自动开始预览mPreviewRequest = mPreviewRequestBuilder.build();startPreview();}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession session) {Log.e(TAG, "ConfigureFailed. session: mCaptureSession");}}, null); // handle 传入 null 表示使用当前线程的 Looper} catch (CameraAccessException e) {e.printStackTrace();}}/*** 预览并录像*/private void initRecordPreviewRequest() {if (mCameraDevice == null || mPreviewSurfaceTexture == null) {return;}try {setUpMediaRecorder();mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);mPreviewSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());mPreviewSurface = new Surface(mPreviewSurfaceTexture);mPreviewRequestBuilder.addTarget(mPreviewSurface);mPreviewRequestBuilder.addTarget(mImageReader.getSurface());//设置ImageReader为输出Surface// Set up Surface for the MediaRecordermPreviewRequestBuilder.addTarget(mediaRecorder.getSurface());//设置MediaRecorder为输出SurfacemCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface, mImageReader.getSurface(), mediaRecorder.getSurface()),new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {mCaptureSession = cameraCaptureSession;updatePreview(mPreviewRequestBuilder);mediaRecorder.start();isRecording = true;}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {Log.e(TAG, "ConfigureFailed. session: mCaptureSession");}}, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();}}/*** 设置录像*/private void setUpMediaRecorder() {try {if (mediaRecorder == null) {mediaRecorder = new MediaRecorder();}mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);startRecordTime = System.currentTimeMillis();filePath = AppUtils.VIDEO_PREFIX + DateUtils.millisecondToDate2(startRecordTime) + ".mp4";File targetFile = new File(filePath);Log.e(TAG, targetFile.getAbsolutePath());mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);mediaRecorder.setVideoSize(MediaInfo.PRIVIEW_WIDTH, MediaInfo.PRIVIEW_HEIGHT);mediaRecorder.setVideoFrameRate(30);mediaRecorder.setOutputFile(targetFile.getAbsolutePath());mediaRecorder.setVideoEncodingBitRate(MEDIA_QUALITY_HIGH);switch (mDeviceOrientation) {case SENSOR_ORIENTATION_DEFAULT_DEGREES:mediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation));break;case SENSOR_ORIENTATION_INVERSE_DEGREES:mediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation));break;}mediaRecorder.prepare();} catch (Exception e) {e.printStackTrace();}}/*** 开启预览*/public void startPreview() {Log.v(TAG, "startPreview");int bufSize = MediaInfo.PRIVIEW_WIDTH * MediaInfo.PRIVIEW_HEIGHT * 3 / 2;data = new byte[bufSize];if (mCaptureSession == null || mPreviewRequestBuilder == null) {Log.w(TAG, "startPreview: mCaptureSession or mPreviewRequestBuilder is null");return;}try {// 开始预览,即一直发送预览的请求mCaptureSession.setRepeatingRequest(mPreviewRequest, null, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();}}/*** 更新预览画面* @param recordBuilder*/private void updatePreview(CaptureRequest.Builder recordBuilder) {if (null == mCameraDevice) {return;}try {setUpCaptureRequestBuilder(recordBuilder);HandlerThread thread = new HandlerThread("CameraPreview");thread.start();mCaptureSession.setRepeatingRequest(recordBuilder.build(), null, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();}}private void setUpCaptureRequestBuilder(CaptureRequest.Builder requestBuilder) {requestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);}public void stopPreview() {Log.v(TAG, "stopPreview");if (mCaptureSession == null || mPreviewRequestBuilder == null) {Log.w(TAG, "stopPreview: mCaptureSession or mPreviewRequestBuilder is null");return;}try {mCaptureSession.stopRepeating();} catch (CameraAccessException e) {e.printStackTrace();}}/*** 拍照*/public void captureStillPicture() {try {CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);captureBuilder.addTarget(mImageReader.getSurface());captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getJpegOrientation(mDeviceOrientation));// 预览如果有放大,拍照的时候也应该保存相同的缩放Rect zoomRect = mPreviewRequestBuilder.get(CaptureRequest.SCALER_CROP_REGION);if (zoomRect != null) {captureBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoomRect);}mCaptureSession.stopRepeating();mCaptureSession.abortCaptures();final long time = System.currentTimeMillis();mCaptureSession.capture(captureBuilder.build(), new CameraCaptureSession.CaptureCallback() {@Overridepublic void onCaptureCompleted(@NonNull CameraCaptureSession session,@NonNull CaptureRequest request,@NonNull TotalCaptureResult result) {Log.w(TAG, "onCaptureCompleted, time: " + (System.currentTimeMillis() - time));try {mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);mCaptureSession.capture(mPreviewRequestBuilder.build(), null, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();}startPreview();}}, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();}}/*** 获取拍照时的角度* @param deviceOrientation* @return*/private int getJpegOrientation(int deviceOrientation) {if (deviceOrientation == OrientationEventListener.ORIENTATION_UNKNOWN) return 0;int sensorOrientation = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);// Round device orientation to a multiple of 90deviceOrientation = (deviceOrientation + 45) / 90 * 90;// Reverse device orientation for front-facing camerasboolean facingFront = mCameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT;if (facingFront) deviceOrientation = -deviceOrientation;// Calculate desired JPEG orientation relative to camera orientation to make// the image upright relative to the device orientationint jpegOrientation = (sensorOrientation + deviceOrientation + 360) % 360;Log.d(TAG, "jpegOrientation: " + jpegOrientation);return jpegOrientation;}/*** 是否为前置摄像头* @return*/public boolean isFrontCamera() {return mCameraId == CameraCharacteristics.LENS_FACING_BACK;}/*** 获取预览尺寸* @return*/public Size getPreviewSize() {return mPreviewSize;}/*** 切换摄像头* @param width* @param height*/public void switchCamera(int width, int height) {mCameraId ^= 1;Log.d(TAG, "switchCamera: mCameraId: " + mCameraId);releaseCamera();openCamera(width, height);}/*** 获取合适的尺寸* @param sizes* @param viewWidth* @param viewHeight* @param previewWidth* @param previewHeight* @return*/private Size chooseOptimalSize(Size[] sizes, int viewWidth, int viewHeight, int previewWidth, int previewHeight) {int totalRotation = getRotation();boolean swapRotation = totalRotation == 90 || totalRotation == 270;int width = swapRotation ? viewHeight : viewWidth;int height = swapRotation ? viewWidth : viewHeight;return getSuitableSize(sizes, width, height, previewWidth, previewHeight);}/*** 获取翻转角度* @return*/private int getRotation() {int displayRotation = 0;rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();switch (rotation) {case Surface.ROTATION_0:displayRotation = 90;break;case Surface.ROTATION_90:displayRotation = 0;break;case Surface.ROTATION_180:displayRotation = 270;break;case Surface.ROTATION_270:displayRotation = 180;break;}int sensorOrientation = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);mDisplayRotate = (displayRotation + sensorOrientation + 270) % 360;return mDisplayRotate;}/*** 获取相机与屏幕支持的最大尺寸* @param sizes* @param width* @param height* @param previewWidth* @param previewHeight* @return*/private Size getSuitableSize(Size[] sizes, int width, int height, int previewWidth, int previewHeight) {int minDelta = Integer.MAX_VALUE; // 最小的差值,初始值应该设置大点保证之后的计算中会被重置int index = 0; // 最小的差值对应的索引坐标float aspectRatio = previewHeight * 1.0f / previewWidth;Log.d(TAG, "getSuitableSize. aspectRatio: " + aspectRatio);for (int i = 0; i < sizes.length; i++) {Size size = sizes[i];Log.e(TAG, "width: " + size.getWidth() + ", height: " + size.getHeight());// 先判断比例是否相等if (size.getWidth() * aspectRatio == size.getHeight()) {int delta = Math.abs(width - size.getWidth());if (delta == 0) {return size;}if (minDelta > delta) {minDelta = delta;index = i;}}}return sizes[index];}/*** 预览画面缩放* @param isZoomIn*/public void handleZoom(boolean isZoomIn) {if (mCameraDevice == null || mCameraCharacteristics == null || mPreviewRequestBuilder == null) {return;}int maxZoom = mCameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM).intValue()* 10;Log.d(TAG, "handleZoom: maxZoom: " + maxZoom);Rect rect = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);if (isZoomIn && mZoom < maxZoom) {mZoom++;} else if (mZoom > 1) {mZoom--;}Log.d(TAG, "handleZoom: mZoom: " + mZoom);int minW = rect.width() / maxZoom;int minH = rect.height() / maxZoom;int difW = rect.width() - minW;int difH = rect.height() - minH;int cropW = difW * mZoom / 100;int cropH = difH * mZoom / 100;cropW -= cropW & 3;cropH -= cropH & 3;Log.d(TAG, "handleZoom: cropW: " + cropW + ", cropH: " + cropH);Rect zoomRect = new Rect(cropW, cropH, rect.width() - cropW, rect.height() - cropH);mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoomRect);mPreviewRequest = mPreviewRequestBuilder.build();startPreview(); // 需要重新 start preview 才能生效}/*** 点击对焦* @param x* @param y* @param width* @param height*/public void focusOnPoint(double x, double y, int width, int height) {if (mCameraDevice == null || mPreviewRequestBuilder == null) {return;}// 1. 先取相对于view上面的坐标int previewWidth = mPreviewSize.getWidth();int previewHeight = mPreviewSize.getHeight();if (mDisplayRotate == 90 || mDisplayRotate == 270) {previewWidth = mPreviewSize.getHeight();previewHeight = mPreviewSize.getWidth();}// 2. 计算摄像头取出的图像相对于view放大了多少,以及有多少偏移double tmp;double imgScale;double verticalOffset = 0;double horizontalOffset = 0;if (previewHeight * width > previewWidth * height) {imgScale = width * 1.0 / previewWidth;verticalOffset = (previewHeight - height / imgScale) / 2;} else {imgScale = height * 1.0 / previewHeight;horizontalOffset = (previewWidth - width / imgScale) / 2;}// 3. 将点击的坐标转换为图像上的坐标x = x / imgScale + horizontalOffset;y = y / imgScale + verticalOffset;if (90 == mDisplayRotate) {tmp = x;x = y;y = mPreviewSize.getHeight() - tmp;} else if (270 == mDisplayRotate) {tmp = x;x = mPreviewSize.getWidth() - y;y = tmp;}// 4. 计算取到的图像相对于裁剪区域的缩放系数,以及位移Rect cropRegion = mPreviewRequestBuilder.get(CaptureRequest.SCALER_CROP_REGION);if (cropRegion == null) {Log.w(TAG, "can't get crop region");cropRegion = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);}int cropWidth = cropRegion.width();int cropHeight = cropRegion.height();if (mPreviewSize.getHeight() * cropWidth > mPreviewSize.getWidth() * cropHeight) {imgScale = cropHeight * 1.0 / mPreviewSize.getHeight();verticalOffset = 0;horizontalOffset = (cropWidth - imgScale * mPreviewSize.getWidth()) / 2;} else {imgScale = cropWidth * 1.0 / mPreviewSize.getWidth();horizontalOffset = 0;verticalOffset = (cropHeight - imgScale * mPreviewSize.getHeight()) / 2;}// 5. 将点击区域相对于图像的坐标,转化为相对于成像区域的坐标x = x * imgScale + horizontalOffset + cropRegion.left;y = y * imgScale + verticalOffset + cropRegion.top;double tapAreaRatio = 0.1;Rect rect = new Rect();rect.left = clamp((int) (x - tapAreaRatio / 2 * cropRegion.width()), 0, cropRegion.width());rect.right = clamp((int) (x + tapAreaRatio / 2 * cropRegion.width()), 0, cropRegion.width());rect.top = clamp((int) (y - tapAreaRatio / 2 * cropRegion.height()), 0, cropRegion.height());rect.bottom = clamp((int) (y + tapAreaRatio / 2 * cropRegion.height()), 0, cropRegion.height());// 6. 设置 AF、AE 的测光区域,即上述得到的 rectmPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, new MeteringRectangle[]{new MeteringRectangle(rect, 1000)});mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_REGIONS, new MeteringRectangle[]{new MeteringRectangle(rect, 1000)});mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_START);try {// 7. 发送上述设置的对焦请求,并监听回调mCaptureSession.capture(mPreviewRequestBuilder.build(), mAfCaptureCallback, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();}}private final CameraCaptureSession.CaptureCallback mAfCaptureCallback = new CameraCaptureSession.CaptureCallback() {private void process(CaptureResult result) {Integer state = result.get(CaptureResult.CONTROL_AF_STATE);if (null == state) {return;}Log.d(TAG, "process: CONTROL_AF_STATE: " + state);if (state == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || state == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {Log.d(TAG, "process: start normal preview");mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.FLASH_MODE_OFF);startPreview();}}@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);}};private void startBackgroundThread() {if (mBackgroundThread == null || mBackgroundHandler == null) {Log.v(TAG, "startBackgroundThread");mStop = false;mBackgroundThread = new HandlerThread("CameraBackground");mBackgroundThread.start();mBackgroundHandler = new Handler(mBackgroundThread.getLooper());}}/***  开启数据发送线程*/private void startSendDataThread() {if (mDataSendThread == null) {mDataSendThread = new DataSendThread();mDataSendThread.start();mStop = false;}}private void stopBackgroundThread() {Log.v(TAG, "stopBackgroundThread");mBackgroundThread.quitSafely();try {mStop = true;mBackgroundThread.join();mBackgroundThread = null;mBackgroundHandler = null;} catch (InterruptedException e) {e.printStackTrace();}}private int clamp(int x, int min, int max) {if (x > max) return max;if (x < min) return min;return x;}/***  实时回调数据下发C层线程*/class DataSendThread extends Thread {public DataSendThread() {super("Camera DataSendThread");}@Overridepublic void run() {// TODO Auto-generated method stubandroid.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);Thread.currentThread().setPriority(Thread.MAX_PRIORITY);long curFrameBegingTime = 0;long curFrameTimeout = 0;while (!mStop) {try {curFrameBegingTime = SystemClock.elapsedRealtimeNanos();while (data == null && !mStop)ThreadUtils.sleep(10);if (mStop)break;if (data != null) {if (Constants.PORTRAIT_MODE) {byte[] rotate90 = new byte[data.length];rotateYv12Degree90(data,MediaInfo.PRIVIEW_WIDTH,MediaInfo.PRIVIEW_HEIGHT,rotate90,true);byte[] cut = yuvCroper.crop(rotate90);if (cut[0] != 0)JniInterface.SendVideoData(cut,MediaInfo.PRIVIEW_HEIGHT, cropH, 0);if (GloableConfig.getInstance().getRecordState() == Constants.STATUS_ENABLE&& null != JMediaRecorder.getInstance().getmMediaRecorder()&& null != JMediaRecorder.getInstance().getmMediaRecorder().getVideoEncoder()) {VideoEncoder.onPreviewCallbackWithBuffer(rotate90, 0);}} else {JniInterface.SendVideoData(data,MediaInfo.PRIVIEW_WIDTH, MediaInfo.PRIVIEW_HEIGHT, 0);if (GloableConfig.getInstance().getRecordState() == Constants.STATUS_ENABLE&& null != JMediaRecorder.getInstance().getmMediaRecorder()&& null != JMediaRecorder.getInstance().getmMediaRecorder().getVideoEncoder()) {VideoEncoder.onPreviewCallbackWithBuffer(data, 0);}}}long left = 1000000000/ MediaInfo.PRIVIEW_FRAME_RATE- (SystemClock.elapsedRealtimeNanos() - curFrameBegingTime);if (left > 0) {left -= curFrameTimeout;curFrameTimeout = 0;if (left > 0)ThreadUtils.sleep(left / 1000000,(int) (left % 1000000));} elsecurFrameTimeout = -left;} catch (Exception e) {e.printStackTrace();}}}}private void rotateYv12Degree90(byte[] src, int width, int height, byte[] dst, boolean clockwise) {int area = width * height;if (clockwise) {rotateRectClockwiseDegree90(src, 0, width, height, dst, 0);rotateRectClockwiseDegree90(src, area, width / 2, height / 2, dst, area);rotateRectClockwiseDegree90(src, area * 5 / 4, width / 2, height / 2, dst, area * 5 / 4);} else {rotateRectAnticlockwiseDegree90(src, 0, width, height, dst, 0);rotateRectAnticlockwiseDegree90(src, area, width / 2, height / 2, dst, area);rotateRectAnticlockwiseDegree90(src, area * 5 / 4, width / 2, height / 2, dst, area * 5 / 4);}}private void rotateRectClockwiseDegree90(byte[] src, int srcOffset, int width, int height, byte dst[], int dstOffset) {int i, j;int index = dstOffset;for (i = 0; i < width; i++) {for (j = height - 1; j >= 0; j--) {dst[index] = src[srcOffset + j * width + i];index++;}}}private void rotateRectAnticlockwiseDegree90(byte[] src, int srcOffset, int width, int height, byte dst[],int dstOffset) {int i, j;int index = dstOffset;for (i = width - 1; i >= 0; i--) {for (j = 0; j < height; j++) {dst[index] = src[srcOffset + j * width + i];index++;}}}
}

3.0回调的数据处理

/**** 此方法内注释以640*480为例* 未考虑CropRect的*/public static byte[] getBytesFromImageAsType(Image image, int type) {try {//获取源数据,如果是YUV格式的数据planes.length = 3//plane[i]里面的实际数据可能存在byte[].length <= capacity (缓冲区总大小)final Image.Plane[] planes = image.getPlanes();//数据有效宽度,一般的,图片width <= rowStride,这也是导致byte[].length <= capacity的原因// 所以我们只取width部分int width = image.getWidth();int height = image.getHeight();//此处用来装填最终的YUV数据,需要1.5倍的图片大小,因为Y U V 比例为 4:1:1byte[] yuvBytes = new byte[width * height * ImageFormat.getBitsPerPixel(ImageFormat.YV12) / 8];//目标数组的装填到的位置int dstIndex = 0;//临时存储uv数据的byte uBytes[] = new byte[width * height / 4];byte vBytes[] = new byte[width * height / 4];int uIndex = 0;int vIndex = 0;int pixelsStride, rowStride;for (int i = 0; i < planes.length; i++) {pixelsStride = planes[i].getPixelStride();rowStride = planes[i].getRowStride();ByteBuffer buffer = planes[i].getBuffer();//如果pixelsStride==2,一般的Y的buffer长度=640*480,UV的长度=640*480/2-1//源数据的索引,y的数据是byte中连续的,u的数据是v向左移以为生成的,两者都是偶数位为有效数据byte[] bytes = new byte[buffer.capacity()];buffer.get(bytes);int srcIndex = 0;if (i == 0) {//直接取出来所有Y的有效区域,也可以存储成一个临时的bytes,到下一步再copyfor (int j = 0; j < height; j++) {System.arraycopy(bytes, srcIndex, yuvBytes, dstIndex, width);srcIndex += rowStride;dstIndex += width;}} else if (i == 1) {//根据pixelsStride取相应的数据for (int j = 0; j < height / 2; j++) {for (int k = 0; k < width / 2; k++) {uBytes[uIndex++] = bytes[srcIndex];srcIndex += pixelsStride;}if (pixelsStride == 2) {srcIndex += rowStride - width;} else if (pixelsStride == 1) {srcIndex += rowStride - width / 2;}}} else if (i == 2) {//根据pixelsStride取相应的数据for (int j = 0; j < height / 2; j++) {for (int k = 0; k < width / 2; k++) {vBytes[vIndex++] = bytes[srcIndex];srcIndex += pixelsStride;}if (pixelsStride == 2) {srcIndex += rowStride - width;} else if (pixelsStride == 1) {srcIndex += rowStride - width / 2;}}}}image.close();//根据要求的结果类型进行填充switch (type) {case YV12:System.arraycopy(uBytes, 0, yuvBytes, dstIndex, uBytes.length);System.arraycopy(vBytes, 0, yuvBytes, dstIndex + uBytes.length, vBytes.length);break;case YUV420SP:for (int i = 0; i < vBytes.length; i++) {yuvBytes[dstIndex++] = uBytes[i];yuvBytes[dstIndex++] = vBytes[i];}break;case NV21:for (int i = 0; i < vBytes.length; i++) {yuvBytes[dstIndex++] = vBytes[i];yuvBytes[dstIndex++] = uBytes[i];}break;}return yuvBytes;} catch (final Exception e) {if (image != null) {image.close();}}return null;}

4.0自定义Camera2TextureView

public class Camera2TextureView extends TextureView {private static final String TAG = "CameraTextureView";private Camera2Proxy mCameraProxy;private Context mContext;public Camera2TextureView(Context context) {this(context, null);}public Camera2TextureView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public Camera2TextureView(Context context, AttributeSet attrs, int defStyleAttr) {this(context, attrs, defStyleAttr, 0);}public Camera2TextureView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);init(context);}private void init(Context context) {this.mContext = context;setSurfaceTextureListener(mSurfaceTextureListener);mCameraProxy = new Camera2Proxy((Activity) context);}private SurfaceTextureListener mSurfaceTextureListener = new SurfaceTextureListener() {@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {Log.v(TAG, "onSurfaceTextureAvailable. width: " + width + ", height: " + height);mCameraProxy.setPreviewSurface(surface);mCameraProxy.openCamera(MediaInfo.PRIVIEW_WIDTH, MediaInfo.PRIVIEW_HEIGHT);configureTransform(width, height);// resize TextureViewint previewWidth = mCameraProxy.getPreviewSize().getWidth();int previewHeight = mCameraProxy.getPreviewSize().getHeight();if (width > height) {setAspectRatio(previewWidth, previewHeight);} else {setAspectRatio(previewHeight, previewWidth);}}@Overridepublic void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {Log.v(TAG, "onSurfaceTextureSizeChanged. width: " + width + ", height: " + height);}@Overridepublic boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {Log.v(TAG, "onSurfaceTextureDestroyed");mCameraProxy.releaseCamera();return false;}@Overridepublic void onSurfaceTextureUpdated(SurfaceTexture surface) {}};private void setAspectRatio(int width, int height) {if (width < 0 || height < 0) {throw new IllegalArgumentException("Size cannot be negative.");}requestLayout();}public Camera2Proxy getCameraProxy() {return mCameraProxy;}/*** 全屏翻转* @param viewWidth* @param viewHeight*/private void configureTransform(int viewWidth, int viewHeight) {if (null == mCameraProxy.getPreviewSize() || null == mContext) {return;}int rotation = ((Activity)mContext).getWindowManager().getDefaultDisplay().getRotation();Point point = new Point();((Activity) mContext).getWindowManager().getDefaultDisplay().getSize(point);Matrix matrix = new Matrix();RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);RectF bufferRect = new RectF(0, 0, point.y, point.x);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 scale1 = Math.max((float) viewHeight /point.y,(float) viewWidth / point.x);float scale2 = Math.min((float) point.y / viewHeight,(float) point.x / viewWidth);matrix.postScale(scale1, scale2, centerX, centerY);matrix.postRotate(270 * rotation, centerX, centerY);}setTransform(matrix);}
}

5.0问题

当只进行实时画面的预览的时候,画面流畅,但加了回调算法处理后,画面会出现卡顿,帧率降低。性能差的手机帧率只有8帧左右,这是远远达不到算法检测要求的,性能好点的手机能达到60帧(两个手机都是华为的,一个是办电信网送的,一个是荣耀10),对手机性能要求较高。

四、效果

竖屏(不检测)效果:

横屏检测效果:

Camera2实现预览及算法处理相关推荐

  1. Android 音视频开发(三) -- Camera2 实现预览、拍照功能

    音视频 系列文章 Android 音视频开发(一) – 使用AudioRecord 录制PCM(录音):AudioTrack播放音频 Android 音视频开发(二) – Camera1 实现预览.拍 ...

  2. Android OpenGL+Camera2渲染(2) —— OpenGL实现Camera2图像预览

    Android OpenGL+Camera2渲染(1) -- OpenGL简单介绍 Android OpenGL+Camera2渲染(2) -- OpenGL实现Camera2图像预览 Android ...

  3. Android Camera2 相机预览、获取数据

    Camera2简要说明 在Google 推出Android 5.0的时候, Android Camera API 版本升级到了API2(android.hardware.camera2),大幅提高了A ...

  4. Camera2 三预览

    1 获取预览尺寸 CameraCharacteristics 是一个只读的相机信息提供者,其内部携带大量的相机信息,包括代表相机朝向的 LENS_FACING:判断闪光灯是否可用的 FLASH_INF ...

  5. 使用Camera2实现预览功能

    前言 首先需要知道的是该博客只是简单的将摄像头打开并进行预览的一个操作,对于横屏竖屏切换的一个判断处理并没有实现,后续会进行完善,但是不会在这个博客中进行说明. 其次在编写之前应该对整个预览过程用到的 ...

  6. Android Camera2相机预览画面放大缩小(数码变焦DigitalZoom)功能实现

    一.前言 Android自定义相机开发中,常常会有通过手势放大或缩小相机预览画面的需求,即数码变焦DigitalZoom. 二.接口说明 1. 获取最大的放大倍数 float maxZoom = mC ...

  7. 从零开发一款相机APP 第七篇: Camera2相机 预览功能实现

    本课程内容由 @小驰笔记 出品,欢迎关注,获取更多交流信息~ 欢迎访问个人博客:www.xiaochibiji.com 显示需要借助surface,一般采用surfaceview或者texturevi ...

  8. Android Camera2教程之打开相机、开启预览、实现PreviewCallback、拍照

    转载请注明出处: http://blog.csdn.net/lb377463323/article/details/52740411 Android API 21新增了Camera2,这与之前的cam ...

  9. Camera2预览拍照流程

    Camera2是现在Andoird相机开发中经常使用的框架,最近一直在学习Camera2的使用,今天简单分享一下我学到的Camera2的预览拍照的流程. 1.获取相机服务,在Camera2中相机服务的 ...

最新文章

  1. 树莓派.系统.官方下载中NOOBS和Raspbian的区别
  2. django(七)之数据库表的单表-增删改查QuerySet,双下划线
  3. 【干货】工作邮件高段位写法
  4. java怎么用return代替else_java – 从一个隐含或明确的“else”方法返回,还是用一个“return”语句返回?...
  5. C++ string assign()赋值常用方法
  6. OpenCV与图像处理学习十五——LBP纹理特征(含代码)
  7. 南京大学获赠1.2亿!
  8. 【Elasticsearch】es Root mapping definition has unsupported parameters
  9. [转]解决Sublime Text 2中文显示乱码问题
  10. 两化融合管理体系评定申请表概况
  11. 一台设备驱动万物:苹果和三星即将推陈出新
  12. [转载]在Java应用程序中访问USB设备
  13. “姓氏文化展”在国图开展 免费对读者开放
  14. 【第九篇】商城系统-商城首页功能
  15. x.norm(p=2,dim=1,keepdim=True)
  16. 论文翻译 Joint Discriminative and Generative Learning for Person Re-identification
  17. Apsara Clouder云计算专项技能认证:云服务器ECS入门[考试真题分享]
  18. flv怎么转换成mp4?这3种方法总有适合你的
  19. CPU到底是怎么识别代码的?
  20. pdf转换工具怎么用?手把手教会你~

热门文章

  1. html ajax打包成app,利用HTML5与ajax完成拖拽上传文件
  2. 微信小程序引入ECharts,并自定义动态修改表内参数配置
  3. 少年,不怕!   emacs for erlang --- so easy
  4. 2022年福建省安全保护服务人员(初级保安员)考试练习题及答案
  5. SAP Web Service 日志监控
  6. 机器学习算法(四)逻辑回归理论与python实现+经典示例(从疝气病预测病马的死亡率)
  7. 历史上影响最大的10首诗
  8. 2774 火烧赤壁(排序贪心)
  9. java算法题Day9
  10. 麻将算法之 ------ 胡牌算法