Android Camera-CameraView源码分析

2,638 阅读7分钟

在前面文章中已经介绍了如何使用 CameraView,这是 Google 官方提供的库,并有相关 demo,因为 Android 的碎片化太严重,官方也是考虑到这些,才提供了CameraView供大家学习和参考。

源码分析

源码结构

源码结构
根据官方的说明:

API Level Camera API Preview View
9-13 Camera1 SurfaceView
14-20 Camera1 TextureView
21-23 Camera2 TextureView
24 Camera2 SurfaceView

具体的实现都在CameraView类中。

类图

源码中涉及到的主要类之间的关系,是从最新的源码中查看到(跟官网的表格不太一样,最新的源码sdk最低版本为14):

CameraView

  • Camera 区分:Android5.0(21)以下使用 Camera1,以上使用 Camera2
  • Preview View:Android6.0(23)以上使用SurfaceView(SurfaceView在Android7.0上增加了新特性(平移、旋转等)),这里应该是 Android7.0以上(>23)使用SurfaceView,其他都使用TextureView,最新的源码sdk最低版本要求14。

CameraView

CameraView使用非常简单,在 CameraView 使用那篇文章已经做过详细说明。

初始化

<com.google.android.cameraview.CameraView
    android:id="@+id/camera"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:keepScreenOn="true"
    android:adjustViewBounds="true"
    app:autoFocus="true"
    app:aspectRatio="4:3"
    app:facing="back"
    app:flash="auto"/>

直接看下CameraView的构造函数:

public CameraView(Context context, AttributeSet attrs, int defStyleAttr) {
   super(context, attrs, defStyleAttr);
   ...
   // Internal setup
   // 1.创建预览视图
   final PreviewImpl preview = createPreviewImpl(context);
   mCallbacks = new CallbackBridge();
   // 2.根据 Android SDK 版本选择不同的 Camera
   if (Build.VERSION.SDK_INT < 21) {
      mImpl = new Camera1(mCallbacks, preview);
   } else if (Build.VERSION.SDK_INT < 23) {
      mImpl = new Camera2(mCallbacks, preview, context);
   } else {
      mImpl = new Camera2Api23(mCallbacks, preview, context);
   }
   // Attributes
   // 3. 读取自定义 View 属性, 设置相机摄像头位置、预览画面比例、对焦方式、闪光灯
   TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraView, defStyleAttr,
            R.style.Widget_CameraView);
   mAdjustViewBounds = a.getBoolean(R.styleable.CameraView_android_adjustViewBounds, false);
   setFacing(a.getInt(R.styleable.CameraView_facing, FACING_BACK));
   String aspectRatio = a.getString(R.styleable.CameraView_aspectRatio);
   if (aspectRatio != null) {
      setAspectRatio(AspectRatio.parse(aspectRatio));
   } else {
      setAspectRatio(Constants.DEFAULT_ASPECT_RATIO);
   }
   setAutoFocus(a.getBoolean(R.styleable.CameraView_autoFocus, true));
   setFlash(a.getInt(R.styleable.CameraView_flash, Constants.FLASH_AUTO));
   a.recycle();
   // Display orientation detector
   // 4. 增加旋转方向监听,设置相机的旋转方向
   mDisplayOrientationDetector = new DisplayOrientationDetector(context) {
      @Override
      public void onDisplayOrientationChanged(int displayOrientation) {
            mImpl.setDisplayOrientation(displayOrientation);
      }
   };
}

createPreviewImpl实现:

private PreviewImpl createPreviewImpl(Context context) {
   PreviewImpl preview;
   if (Build.VERSION.SDK_INT >= 23) {
      preview = new SurfaceViewPreview(context, this);
   } else {
      preview = new TextureViewPreview(context, this);
   }
   return preview;
}

这里主要代码角度看下CameraView版本选择策略,在最开始的已经说明。
Camera的选择:api <21的,使用Camera1,>=21使用 Camera2,这里完全没有问题。
Camera Preview的选择,存在几个疑问,api >=23使用SurfaceView,不过api 24的时候使用SurfaceView才是官方推荐的。

Starting in platform version N, SurfaceView’s window position is updated synchronously with other View rendering. This means that translating and scaling a SurfaceView on screen will not cause rendering artifacts. Such artifacts may occur on previous versions of the platform when its window is positioned asynchronously.

这里应该 api >23使用SurfaceView,其他情况使用TextureView,因为在工程中 定义了minSdkVersion = 14,api 14-23使用TextureView,表格可以更新为:

API Level Camera API Preview View
14-20 Camera1 TextureView
21-23 Camera2 TextureView
24 Camera2 SurfaceView

Preview定义

PreviewImpl封装了预览控件的操作方法,SurfaceViewPreviewTextureViewPreview分别对应SurfaceViewTextureViewPreviewImpl实现。

SurfaceViewPreview

实现很简单,直接加载有SurfaceView控件的布局,并封装了SurfaceHolder操作

TextureViewPreview

直接加载有TextureView控件布局,并监听TextureView.SurfaceTextureListener

Camera定义

CameraViewImpl定义了相机的各种操作,Camera1Camera2Camera2Api23都是CameraViewImpl的具体实现。
看下它们的构造函数:
Camera1

Camera1(Callback callback, PreviewImpl preview) {
   super(callback, preview);
   preview.setCallback(new PreviewImpl.Callback() {
      @Override
      public void onSurfaceChanged() {
            if (mCamera != null) {
               setUpPreview();
               adjustCameraParameters();
            }
      }
   });
}

增加了 Callback,当SurfaceChanged的时候,设置预览和 Camera 参数。
Camera2

Camera2(Callback callback, PreviewImpl preview, Context context) {
   super(callback, preview);
   mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
   mPreview.setCallback(new PreviewImpl.Callback() {
      @Override
      public void onSurfaceChanged() {
            startCaptureSession();
      }
   });
}

1.初始化了CameraManager
2.增加Callback,当SurfaceChanged的时候,对CaptureSession进行设置

Camera2Api23继承了Camera2,重用了Camera2的构造。
Camera1和 Camera2的实现和使用流程,可以参考前面的几篇文章,接下来介绍CameraView的几个重要方法的实现。

start

public void start() {
   if (!mImpl.start()) {
      //store the state ,and restore this state after fall back o Camera1
      Parcelable state = onSaveInstanceState();
      // Camera2 uses legacy hardware layer; fall back to Camera1
      mImpl = new Camera1(mCallbacks, createPreviewImpl(getContext()));
      onRestoreInstanceState(state);
      mImpl.start();
   }
}

开启相机,首先判断是否可以开启,如果不能开启,默认会使用 Camera1。

Camera1.start

boolean start() {
   //1. 选择摄像头
   chooseCamera();
   //2. 打开摄像头
   openCamera();
   //3. 设置预览
   if (mPreview.isReady()) {
      setUpPreview();
   }
   mShowingPreview = true;
   //4. 开始预览
   mCamera.startPreview();
   return true;
}

具体的流程,可以参考Camera1使用,这里介绍几个重要的方法。
1.chooseCamera会遍历所有摄像头,然后根据CameraView初始化时传入的值进行对比,默认是FACING_BACK后置摄像头。
2.openCamera方法,这里具体展开介绍下:

private void openCamera() {
   if (mCamera != null) {
      releaseCamera();
   }
   //1. 打开摄像头
   mCamera = Camera.open(mCameraId);
   mCameraParameters = mCamera.getParameters();
   // Supported preview sizes
   mPreviewSizes.clear();
   //2. 获取所有支持的预览尺寸
   for (Camera.Size size : mCameraParameters.getSupportedPreviewSizes()) {
      Log.d("DEBUG", "###### SupportedPreviewSizes: width=" + size.width + ", height="
               + size.height);
      mPreviewSizes.add(new Size(size.width, size.height));
   }
   // Supported picture sizes;
   mPictureSizes.clear();
   //3. 获取所有支持的拍照尺寸
   for (Camera.Size size : mCameraParameters.getSupportedPictureSizes()) {
      Log.d("DEBUG", "###### SupportedPictureSizes: width=" + size.width + ", height="
               + size.height);
      mPictureSizes.add(new Size(size.width, size.height));
   }
   // AspectRatio
   if (mAspectRatio == null) {
      mAspectRatio = Constants.DEFAULT_ASPECT_RATIO;
   }
   //4. 设置预览比例
   adjustCameraParameters();
   mCamera.setDisplayOrientation(calcDisplayOrientation(mDisplayOrientation));
   mCallback.onCameraOpened();
}

3.setUpPreview设置预览

void setUpPreview() {
   try {
      if (mPreview.getOutputClass() == SurfaceHolder.class) {
            mCamera.setPreviewDisplay(mPreview.getSurfaceHolder());
      } else {
            mCamera.setPreviewTexture((SurfaceTexture) mPreview.getSurfaceTexture());
      }
   } catch (IOException e) {
      throw new RuntimeException(e);
   }
}

根据mPreview类型的不同,使用SurfaceView预览或者TextureView预览。 4.mCamera.startPreview()开启预览

Camera2.start

boolean start() {
   //1. 选择摄像头,默认后置摄像头
   if (!chooseCameraIdByFacing()) {
      return false;
   }
   //2. 设置预览尺寸,预览尺寸比例,拍照尺寸,其他相关设置
   collectCameraInfo();
   //3. 初始化 ImageReader,并设置回调
   prepareImageReader();
   //4. 打开摄像头
   startOpeningCamera();
   return true;
}

具体流程可以参考Camera2使用,这里只介绍重要方法说明。
1.chooseCameraIdByFacing遍历支持的摄像头列表,根据条件筛选获取到指定的摄像头,默认后置摄像头。
2.collectCameraInfo方法,获取所有支持预览尺寸,和拍照尺寸,并获取支持的预览尺寸比例
3.prepareImageReader方法,初始化ImageReader

private void prepareImageReader() {
   if (mImageReader != null) {
      mImageReader.close();
   }
   Size largest = mPictureSizes.sizes(mAspectRatio).last();
   mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
            ImageFormat.JPEG, /* maxImages */ 2);
   mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, null);
}

设置输出格式为 JPEG,并增加ImageAvailableListener回调监听。
4.startOpeningCamera方法,打开摄像头,并设置CameraDevice.StateCallback监听

private void startOpeningCamera() {
   try {
      mCameraManager.openCamera(mCameraId, mCameraDeviceCallback, null);
   } catch (CameraAccessException e) {
      throw new RuntimeException("Failed to open camera: " + mCameraId, e);
   }
}

private final CameraDevice.StateCallback mCameraDeviceCallback
      = new CameraDevice.StateCallback() {

   @Override
   public void onOpened(@NonNull CameraDevice camera) {
      mCamera = camera;
      mCallback.onCameraOpened();
      //相机打开,开启预览画面
      startCaptureSession();
   }

   @Override
   public void onClosed(@NonNull CameraDevice camera) {
      mCallback.onCameraClosed();
   }

   @Override
   public void onDisconnected(@NonNull CameraDevice camera) {
      mCamera = null;
   }

   @Override
   public void onError(@NonNull CameraDevice camera, int error) {
      Log.e(TAG, "onError: " + camera.getId() + " (" + error + ")");
      mCamera = null;
   }

};

监听相机打开后,开启预览画面,startCaptureSession

void startCaptureSession() {
   if (!isCameraOpened() || !mPreview.isReady() || mImageReader == null) {
      return;
   }
   //1. 选择最合适的预览尺寸
   Size previewSize = chooseOptimalSize();
   mPreview.setBufferSize(previewSize.getWidth(), previewSize.getHeight());
   Surface surface = mPreview.getSurface();
   try {
      //2. 创建预览请求
      mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
      //3. 请求管理 target surface
      mPreviewRequestBuilder.addTarget(surface);
      //4. 创建CaptureSession,并增加Session监听
      mCamera.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
               mSessionCallback, null);
   } catch (CameraAccessException e) {
      throw new RuntimeException("Failed to start camera session");
   }
}

监听CameraCaptureSession状态的回调声明:

private final CameraCaptureSession.StateCallback mSessionCallback
      = new CameraCaptureSession.StateCallback() {

   @Override
   public void onConfigured(@NonNull CameraCaptureSession session) {
      if (mCamera == null) {
            return;
      }
      mCaptureSession = session;
      updateAutoFocus();
      updateFlash();
      try {
         //1. 开启预览,并设置监听回调
            mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(),
                  mCaptureCallback, null);
      } catch (CameraAccessException e) {
            Log.e(TAG, "Failed to start camera preview because it couldn't access camera", e);
      } catch (IllegalStateException e) {
            Log.e(TAG, "Failed to start camera preview.", e);
      }
   }

   @Override
   public void onConfigureFailed(@NonNull CameraCaptureSession session) {
      Log.e(TAG, "Failed to configure capture session.");
   }

   @Override
   public void onClosed(@NonNull CameraCaptureSession session) {
      if (mCaptureSession != null && mCaptureSession.equals(session)) {
            mCaptureSession = null;
      }
   }

};

takePicture

public void takePicture() {
   mImpl.takePicture();
}

根据 api 选择不同的实现

Camera1.takePicture

void takePicture() {
   if (!isCameraOpened()) {
      throw new IllegalStateException(
               "Camera is not ready. Call start() before takePicture().");
   }
   //1. 判断是否自动对焦
   if (getAutoFocus()) {
      mCamera.cancelAutoFocus();
      mCamera.autoFocus(new Camera.AutoFocusCallback() {
            @Override
            public void onAutoFocus(boolean success, Camera camera) {
               //2. 拍照
               takePictureInternal();
            }
      });
   } else {
      //2.拍照
      takePictureInternal();
   }
}

真正执行拍照的方法takePictureInternal

void takePictureInternal() {
   if (!isPictureCaptureInProgress.getAndSet(true)) {
      //1. 拍照增加回调
      mCamera.takePicture(null, null, null, new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
               isPictureCaptureInProgress.set(false);
               //2. 把 data 传到上层的回调
               mCallback.onPictureTaken(data);
               camera.cancelAutoFocus();
               camera.startPreview();
            }
      });
   }
}

takePicture中增加的 jpeg 的PictureCallback回调,返回的data通过callback回调到上层

Camera2.takePicture

void takePicture() {
   //1.判断自动对焦
   if (mAutoFocus) {
      lockFocus();
   } else {
      //2. 拍照请求
      captureStillPicture();
   }
}

//设置对焦
private void lockFocus() {
   mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
            CaptureRequest.CONTROL_AF_TRIGGER_START);
   try {
      mCaptureCallback.setState(PictureCaptureCallback.STATE_LOCKING);
      mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, null);
   } catch (CameraAccessException e) {
      Log.e(TAG, "Failed to lock focus.", e);
   }
}

//PictureCaptureCallback
PictureCaptureCallback mCaptureCallback = new PictureCaptureCallback() {
   @Override
   public void onPrecaptureRequired() {
      mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
               CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
      setState(STATE_PRECAPTURE);
      try {
            mCaptureSession.capture(mPreviewRequestBuilder.build(), this, null);
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
                  CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
      } catch (CameraAccessException e) {
            Log.e(TAG, "Failed to run precapture sequence.", e);
      }
   }

   @Override
   public void onReady() {
      //2. 拍照请求
      captureStillPicture();
   }
};

判断是否自动对焦,最后调用captureStillPicture方法进行拍照请求:

void captureStillPicture() {
   try {
      //1. 创建TEMPLATE_STILL_CAPTURE的Capture请求
      CaptureRequest.Builder captureRequestBuilder = mCamera.createCaptureRequest(
               CameraDevice.TEMPLATE_STILL_CAPTURE);
      //2. 添加target
      captureRequestBuilder.addTarget(mImageReader.getSurface());
      //3. 设置 AF mode
      captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
               mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AF_MODE));
      //4.flash模式设置
      ...
      // Calculate JPEG orientation.
      //5. 计算拍照图片的方向
      @SuppressWarnings("ConstantConditions")
      int sensorOrientation = mCameraCharacteristics.get(
               CameraCharacteristics.SENSOR_ORIENTATION);
      captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION,
               (sensorOrientation +
                        mDisplayOrientation * (mFacing == Constants.FACING_FRONT ? 1 : -1) +
                        360) % 360);
      // Stop preview and capture a still picture.
      //6. 停止预览
      mCaptureSession.stopRepeating();
      //7. 拍照
      mCaptureSession.capture(captureRequestBuilder.build(),
               new CameraCaptureSession.CaptureCallback() {
                  @Override
                  public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                           @NonNull CaptureRequest request,
                           @NonNull TotalCaptureResult result) {
                        //8. 取消对焦
                        unlockFocus();
                  }
               }, null);
   } catch (CameraAccessException e) {
      Log.e(TAG, "Cannot capture a still picture.", e);
   }
}

//取消对焦,重新设置预览
void unlockFocus() {
   mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
            CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
   try {
      mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, null);
      updateAutoFocus();
      updateFlash();
      mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
               CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
      mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback,
               null);
      mCaptureCallback.setState(PictureCaptureCallback.STATE_PREVIEW);
   } catch (CameraAccessException e) {
      Log.e(TAG, "Failed to restart camera preview.", e);
   }
}

拍照后真正的数据回调,是在初始化 ImageReader 的OnImageAvailableListener

private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
            = new ImageReader.OnImageAvailableListener() {
   @Override
   public void onImageAvailable(ImageReader reader) {
      try (Image image = reader.acquireNextImage()) {
            Image.Plane[] planes = image.getPlanes();
            if (planes.length > 0) {
               ByteBuffer buffer = planes[0].getBuffer();
               byte[] data = new byte[buffer.remaining()];
               buffer.get(data);
               //1. 数据回调到上层
               mCallback.onPictureTaken(data);
            }
      }
   }
};

stop

如果不用拍照,或者退出应用,调用 stop 方法

public void stop() {
   mImpl.stop();
}

Camera1.stop

void stop() {
   if (mCamera != null) {
      //1. 停止预览
      mCamera.stopPreview();
   }
   mShowingPreview = false;
   //2. 释放相机资源
   releaseCamera();
}

//释放相机资源
private void releaseCamera() {
   if (mCamera != null) {
      mCamera.release();
      mCamera = null;
      //1. 回调上层
      mCallback.onCameraClosed();
   }
}

Camera2.stop

void stop() {
   //1. 关闭 session
   if (mCaptureSession != null) {
      mCaptureSession.close();
      mCaptureSession = null;
   }
   
   //2. 关闭 Camera
   if (mCamera != null) {
      mCamera.close();
      mCamera = null;
   }
   //3. 关闭 ImageReader
   if (mImageReader != null) {
      mImageReader.close();
      mImageReader = null;
   }
}

CameraView源码相关的分析就结束了,该库很好的封装了 Camera1和 Camera2的使用,本篇文章对其中的重点方法进行了分析,讲解了它的实现原理,希望对大家有所帮助。

参考: