In the previous article, I have introduced how to use CameraView, which is the official library provided by Google and has related demo. Because Android is too fragmented, the official also provides CameraView for your study and reference.

Source code analysis

Source structure

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

The implementation is all in the CameraView class.

The class diagram

The relationships between the main classes involved in the source code are viewed from the latest source code (unlike the table on the official website, the latest source SDK version is 14) :

  • Camera1 is used for Android5.0(21) and Camera2 is used for Android5.0(21)
  • The Preview View: SurfaceView SurfaceView SurfaceView SurfaceView(>23) All others use TextureView, the latest source SDK minimum version 14.

CameraView

Using CameraView is very simple, and using it in CameraView has been explained in detail in that article.

Initialize the

<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"/>
Copy the code

Look directly at the CameraView constructor:

public CameraView(Context context, AttributeSet attrs, int defStyleAttr) {
   super(context, attrs, defStyleAttr); .// Internal setup
   1. Create a preview view
   final PreviewImpl preview = createPreviewImpl(context);
   mCallbacks = new CallbackBridge();
   // 2. Select a Camera based on the Android SDK version
   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. Read from the definition View property, set the camera position, preview picture scale, focus mode, flash
   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. Add the rotation direction monitor and set the camera rotation direction
   mDisplayOrientationDetector = new DisplayOrientationDetector(context) {
      @Override
      public void onDisplayOrientationChanged(int displayOrientation) { mImpl.setDisplayOrientation(displayOrientation); }}; }Copy the code

CreatePreviewImpl implementation:

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;
}
Copy the code

Here’s a look at the CameraView version selection strategy from the main code point of view, as explained at the beginning. Camera select: API <21, use Camera1, >=21 use Camera2, there is no problem at all. API >=23 uses SurfaceView, but API 24 uses SurfaceView.

Starting in platform version N, SurfaceView’s Window Position is updated synchronously with other View rendering. This means that Translating and Moderating 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 uses SurfaceView, otherwise uses TextureView, because minSdkVersion = 14 is defined in the project, API 14-23 uses TextureView, and the table can be updated to:

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

The Preview is defined

PreviewImpl encapsulates the operation method of the preview control. SurfaceViewPreview and TextureViewPreview correspond to the PreviewImpl implementation of SurfaceView and TextureView respectively.

SurfaceViewPreview

The implementation is simple, loading the layout directly with the SurfaceView control and encapsulating the SurfaceHolder operation

TextureViewPreview

Direct load TextureView control layout, and to monitor TextureView. SurfaceTextureListener.

Camera is defined

CameraViewImpl defines various camera operations, Camera1, Camera2, Camera2Api23 are the concrete implementation of CameraViewImpl. Take a look at their constructor: Camera1

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

Added Callback to set preview and Camera parameters while SurfaceChanged. 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(a) { startCaptureSession(); }}); }Copy the code

2. Add Callback to CaptureSession while SurfaceChanged

Camera2Api23 inherits Camera2 and reuses the constructs of Camera2. Camera1 and Camera2 implementation and use process, can refer to the previous several articles, the next introduction to the implementation of several important methods of CameraView.

start

public void start(a) {
   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 = newCamera1(mCallbacks, createPreviewImpl(getContext())); onRestoreInstanceState(state); mImpl.start(); }}Copy the code

To start the camera, check whether it can be started. If it cannot be started, Camera1 is used by default.

Camera1.start

boolean start(a) {
   //1. Select a camera
   chooseCamera();
   //2. Open the camera
   openCamera();
   //3. Set preview
   if (mPreview.isReady()) {
      setUpPreview();
   }
   mShowingPreview = true;
   //4. Start preview
   mCamera.startPreview();
   return true;
}
Copy the code

For details about the process, you can refer to Camera1. Here are some important methods. ChooseCamera will walk through all the cameras and compare them against the value passed in when CameraView is initialized. The default is the FACING_BACK rear camera. 2. OpenCamera method, here is a detailed introduction:

private void openCamera(a) {
   if(mCamera ! =null) {
      releaseCamera();
   }
   //1. Open the camera
   mCamera = Camera.open(mCameraId);
   mCameraParameters = mCamera.getParameters();
   // Supported preview sizes
   mPreviewSizes.clear();
   //2. Get all supported preview sizes
   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. Get all supported camera sizes
   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. Set the preview scale
   adjustCameraParameters();
   mCamera.setDisplayOrientation(calcDisplayOrientation(mDisplayOrientation));
   mCallback.onCameraOpened();
}
Copy the code

3. SetUpPreview set preview

void setUpPreview(a) {
   try {
      if (mPreview.getOutputClass() == SurfaceHolder.class) {
            mCamera.setPreviewDisplay(mPreview.getSurfaceHolder());
      } else{ mCamera.setPreviewTexture((SurfaceTexture) mPreview.getSurfaceTexture()); }}catch (IOException e) {
      throw newRuntimeException(e); }}Copy the code

Use SurfaceView preview or TextureView preview, depending on the mPreview type. 4. McAmera.startpreview () Starts preview

Camera2.start

boolean start(a) {
   //1. Select a camera. By default, the rear camera is installed
   if(! chooseCameraIdByFacing()) {return false;
   }
   //2. Set preview size, preview size ratio, photo size and other related Settings
   collectCameraInfo();
   //3. Initialize ImageReader and set the callback
   prepareImageReader();
   //4. Open the camera
   startOpeningCamera();
   return true;
}
Copy the code

For details, refer to Camera2. Only important methods are introduced here. 1. ChooseCameraIdByFacing go through the list of supported cameras and select the specified camera according to the conditions. The default rear camera is installed. 2. PrepareImageReader method, get all supported preview size, and photo size, and get the supported preview size ratio

private void prepareImageReader(a) {
   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);
}
Copy the code

Set the output format to JPEG, and add ImageAvailableListener callback listener. 4. StartOpeningCamera method, open the camera, and set the CameraDevice. StateCallback listening in

private void startOpeningCamera(a) {
   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();
      // Open the camera, open the preview screen
      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; }};Copy the code

After the monitor camera is turned on, open the preview screen, startCaptureSession

void startCaptureSession(a) {
   if(! isCameraOpened() || ! mPreview.isReady() || mImageReader ==null) {
      return;
   }
   //1. Select the most appropriate preview size
   Size previewSize = chooseOptimalSize();
   mPreview.setBufferSize(previewSize.getWidth(), previewSize.getHeight());
   Surface surface = mPreview.getSurface();
   try {
      //2. Create a preview request
      mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
      //3. Request management target surface
      mPreviewRequestBuilder.addTarget(surface);
      // create CaptureSession and add Session listener
      mCamera.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
               mSessionCallback, null);
   } catch (CameraAccessException e) {
      throw new RuntimeException("Failed to start camera session"); }}Copy the code

Listen for a callback statement from the CameraCaptureSession state:

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. Enable preview and set the listening callback
            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; }}};Copy the code

takePicture

public void takePicture(a) {
   mImpl.takePicture();
}
Copy the code

Choose different implementations depending on the API

Camera1.takePicture

void takePicture(a) {
   if(! isCameraOpened()) {throw new IllegalStateException(
               "Camera is not ready. Call start() before takePicture().");
   }
   //1. Check whether auto focus is enabled
   if (getAutoFocus()) {
      mCamera.cancelAutoFocus();
      mCamera.autoFocus(new Camera.AutoFocusCallback() {
            @Override
            public void onAutoFocus(boolean success, Camera camera) {
               / / 2. Take photostakePictureInternal(); }}); }else {
      / / 2. Take photostakePictureInternal(); }}Copy the code

The actual method to take a photo is takePictureInternal:

void takePictureInternal(a) {
   if(! isPictureCaptureInProgress.getAndSet(true)) {
      //1. Take a photo and add callback
      mCamera.takePicture(null.null.null.new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
               isPictureCaptureInProgress.set(false);
               //2. Pass data to the upper callbackmCallback.onPictureTaken(data); camera.cancelAutoFocus(); camera.startPreview(); }}); }}Copy the code

Add jpeg PictureCallback callback to takePicture, return data via callback to the upper layer

Camera2.takePicture

void takePicture(a) {
   //1. Determine auto focus
   if (mAutoFocus) {
      lockFocus();
   } else {
      //2captureStillPicture(); }}// Set the focus
private void lockFocus(a) {
   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(a) {
      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(a) {
      //2captureStillPicture(); }};Copy the code

Call captureStillPicture ();

void captureStillPicture(a) {
   try {
      //1. Create a Capture request for TEMPLATE_STILL_CAPTURE
      CaptureRequest.Builder captureRequestBuilder = mCamera.createCaptureRequest(
               CameraDevice.TEMPLATE_STILL_CAPTURE);
      / / 2. Add a target
      captureRequestBuilder.addTarget(mImageReader.getSurface());
      //3. Set AF mode
      captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
               mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AF_MODE));
      //4. Set flash mode.// Calculate JPEG orientation.
      //5. Calculate the direction of the photo taken
      @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.
      // stop the preview
      mCaptureSession.stopRepeating();
      / / 7. Take pictures
      mCaptureSession.capture(captureRequestBuilder.build(),
               new CameraCaptureSession.CaptureCallback() {
                  @Override
                  public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                        //8. UnfocusunlockFocus(); }},null);
   } catch (CameraAccessException e) {
      Log.e(TAG, "Cannot capture a still picture.", e); }}// Unfocus and reset preview
void unlockFocus(a) {
   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); }}Copy the code

The actual data callback after the photo is in the OnImageAvailableListener that initializes the ImageReader

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. Data is called back to the upper layermCallback.onPictureTaken(data); }}}};Copy the code

stop

If you don’t need to take a picture or exit the application, call the stop method

public void stop(a) {
   mImpl.stop();
}
Copy the code

Camera1.stop

void stop(a) {
   if(mCamera ! =null) {
      //1. Stop preview
      mCamera.stopPreview();
   }
   mShowingPreview = false;
   //2. Release camera resources
   releaseCamera();
}

// Release the camera resources
private void releaseCamera(a) {
   if(mCamera ! =null) {
      mCamera.release();
      mCamera = null;
      //1mCallback.onCameraClosed(); }}Copy the code

Camera2.stop

void stop(a) {
   / / 1. Close the session
   if(mCaptureSession ! =null) {
      mCaptureSession.close();
      mCaptureSession = null;
   }
   
   / / 2. Close the Camera
   if(mCamera ! =null) {
      mCamera.close();
      mCamera = null;
   }
   / / 3. Close the ImageReader
   if(mImageReader ! =null) {
      mImageReader.close();
      mImageReader = null; }}Copy the code

CameraView source code related analysis is over, the library is a good encapsulation of the use of Camera1 and Camera2, this article focuses on the analysis of the method, explained its implementation principle, I hope to help you.

Reference:

  • Android camera development — CameraView source code parsing
  • Android Camera programming from entry to mastery
  • Android – Google cameraview details