This blog mainly records the Api and some technical calls used in the development process, reading time: 15 minutes +, the content of this blog is relatively simple, only for the development process record.

This article has been exclusively authorized wechat official account: Hongyang (Android) in the wechat official account platform original launch

instructions

In our previous article on Camera, we found that many oF the apis were no longer recommended, and Google’s alternative is Camera2, a new API starting at 5.0 (API Level 21) that gives full control of the Camera on Android devices. Of course, if the product covers Android users with version 4.0, it is still recommended to use Camera. However, in previous cameras, manual control of the Camera was achieved by changing the system, and the API was not friendly. Camera2 at this point in time for the separation of management, Api will be more friendly, clear division of labor.

Thank you very much for the description of this article.

rendering

1. The camera2 concept

Compared with camera, CamerA2 is independent of camera objects in Api. Camera2 uses pipeline method to connect camera device and Android device. The Android Device sends the CaptureRequest request to the Camera Device through the pipe, and the Camera Device returns the CameraMetadata data to the Android Device through the pipe. This all happens in a conversation at CameraCaptureSession.

2. A brief description of the Camera2 API

CameraCaptureSession: CameraCaptureSession: This is a very important API. Whenever an application needs to preview or take a picture, it creates a Session using an instance of this class. The method to control the photography is capture().

CameraDevices: Provides a set of static property information that describes the hardware device and its available Settings and output parameters. Obtained through getCameraCharacteristics.

CameraManager: Manager of all camera devices. To enumerate, query, and turn on available camera devices to turn on and off system cameras, get the CameraManager instance.

CaptureRequest: Defines all the capture parameters that a camera device needs to capture a single image. The request also lists which configured output surfaces should be used as targets for this capture.

CameraDevice: Has a factory method for creating a request builder for a given use case, optimized for the Android device on which the application is running, and describes the system Camera, similar to the early Camera.

CameraRequest Camerarequest.Builder: The CameraRequest parameter is passed when the program calls the setRepeatingRequest() method to preview or capture() to take a picture. CameraRequest represents a capture request, used to describe the capture of a picture of the various parameters set, the program to do a variety of controls on the photo, are set through the CameraRequest parameters. The CameraRequest.Builder is responsible for generating the CameraRequest object.

CameraCharacteristics: Describe the various features of the camera. We can get them from the CameraManager’s getCameraCharacteristics(@nonnull String cameraId) method.

CaptureResult: Describes the result after the photo is taken.

ImageReader: by adding PreviewRequestBuilder addTarget (mImageReader getSurface ()); Yuv data can be obtained in real time in the OnImageAvailableListener interface.

3. The process used by the Camera2 interface

1. After the call openCamera method will callback CameraDevice. StateCallback this method, rewrite onOpened function in this method.

2. Call createCaptureSession in onOpened method, the method and the callback CameraCaptureSession. StateCallback method.

3. CameraCaptureSession. StateCallback rewrite onConfigured methods, set up setRepeatingRequest method (i.e., open the preview).

4. SetRepeatingRequest will callback CameraCaptureSession. CaptureCallback method.

5. Rewrite CameraCaptureSession. CaptureCallback onCaptureCompleted method, the result is the untreated frame data.

4. Customize Camera2

Camera2 like Camera also have cameraId concept, through our mCameraManager. GetCameraIdList () to obtain cameraId list, Then through mCameraManager. GetCameraCharacteristics (id) for each id corresponding to the parameters of the camera.

CameraCharacteristics parameters include:

LENS_FACING: Front camera (LENS_FACING_FRONT) or rear camera (LENS_FACING_BACK).

SENSOR_ORIENTATION: Camera orientation.

FLASH_INFO_AVAILABLE: Indicates whether the flash is supported.

CameraCharacteristics. INFO_SUPPORTED_HARDWARE_LEVEL: get the current camera characteristics of equipment support.

Note: In fact, not all features of Camera2 are available on Android devices from various vendors, Through characteristics. The get (CameraCharacteristics. INFO_SUPPORTED_HARDWARE_LEVEL) method to according to the return value to obtain the support level, specifically:

INFO_SUPPORTED_HARDWARE_LEVEL_FULL: Full hardware support, allowing manual control of full HD cameras, support for continuous shooting mode, and other new features.

INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED: Support for this file is limited. This file needs to be queried separately.

INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY: Supported by all devices, that is, the same features supported by the outdated Camera API.

Using the INFO_SUPPORTED_HARDWARE_LEVEL parameter, we can determine whether to use Camera or Camera

With the above principle explained, the general process may still be a little vague, we directly corresponding to the above logic to start the code.

4.1 Interface Layout



      
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black"
    tools:context="cn.tongue.tonguecamera.ui.CameraActivity">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">// Customize TextureView<cn.tongue.tonguecamera.view.AutoFitTextureView
            android:id="@+id/textureView_g"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </FrameLayout>

    <RelativeLayout
        android:id="@+id/homecamera_bottom_relative2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#00ffffff"
        android:layout_alignParentBottom="true">// Return button<ImageView
            android:id="@+id/iv_back_g"
            android:layout_width="40dp"
            android:layout_height="30dp"
            android:scaleType="centerInside"
            android:layout_marginBottom="20dp"
            android:layout_marginStart="20dp"
            android:layout_centerVertical="true"
            android:background="@drawable/icon_back" />// The photo button<ImageView
            android:id="@+id/img_camera_g"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:scaleType="centerInside"
            android:layout_marginBottom="20dp"
            android:layout_centerInParent="true"
            android:background="@drawable/camera" />

    </RelativeLayout>

</RelativeLayout>

Copy the code

Looking at the above layout we can see that in the Camera2 we did not go ahead and select SurfaceView as the vehicle to render the image, but TextureView.


@RequiresApi(api = Build.VERSION_CODES.M)
    @Override
    public void onResume(a) {
        super.onResume();
        // Start HandlerThread and maintain a handler in the background
        startBackgroundThread();
        // If there is an association, open the camera. If there is no association, bind the event
        if (mTextureView.isAvailable()) {
            openCamera(mTextureView.getWidth(), mTextureView.getHeight());
        } else {
        	// Bind the carrier to listen for eventsmTextureView.setSurfaceTextureListener(mSurfaceTextureListener); }}Copy the code

If you enter the camera for the first time, you need to configure the camera


	/** * Turn on the camera **@paramWidth the width *@paramHeight length * /
    @RequiresApi(api = Build.VERSION_CODES.M)
    private void openCamera(int width, int height) {
    	// Determine the dynamic permissions of the camera whose permissions are above 6.0
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) ! = PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
            return;
        }
        // Configure the preview size of the camera
        setUpCameraOutputs(width, height);
        // Set the Matrix transform to mTextureView
        configureTransform(width, height);
        CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        try {
            if(! mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
                throw new RuntimeException("Time out waiting to lock camera opening.");
            }
            // Open the camera and bind the callback interface
            manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            throw new RuntimeException("Interrupted while trying to lock camera opening.", e); }}Copy the code

SetUpCameraOutputs () set the output parameters of the mImageReader and the callback event OnImageAvailableListener for the data stream And according to the hardware data to see if you need to swap dimensions to get a preview size relative to the sensor.


	/** * call */ when CameraDevice changes state
    private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

		// Turn on event listening
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        @Override
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            // This method is called when the camera is turned on. This is where we start the camera preview.
            mCameraOpenCloseLock.release();
            mCameraDevice = cameraDevice;
            createCameraPreviewSession();
        }

		// Turn off the listener
        @Override
        public void onDisconnected(@NonNull CameraDevice cameraDevice) {
            mCameraOpenCloseLock.release();
            cameraDevice.close();
            mCameraDevice = null;
        }

        @Override
        public void onError(@NonNull CameraDevice cameraDevice, int error) {
            mCameraOpenCloseLock.release();
            cameraDevice.close();
            mCameraDevice = null; finish(); }};Copy the code

From the callback method, we start the preview image, through CameraCaptureSession createCameraPreviewSession () is to create a camera preview.


	/** * Create a new camera preview */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void createCameraPreviewSession(a) {
        try {
            SurfaceTexture texture = mTextureView.getSurfaceTexture();
            // Set the default buffer size to the camera preview size.
            texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
            Surface surface = new Surface(texture);
            // Camera2 is called by creating a request session
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            // Use Surface to set capturerequest.Builder
            mPreviewRequestBuilder.addTarget(surface);
            // Method to create CaptureSession.
            mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
                    new CameraCaptureSession.StateCallback() {

                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                            if (null == mCameraDevice) {
                                return;
                            }
                            mCaptureSession = cameraCaptureSession;
                            try {
                                // Auto zoom is continuous
                                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                                setAutoFlash(mPreviewRequestBuilder);
                                // Show the camera preview
                                mPreviewRequest = mPreviewRequestBuilder.build();
                                // Set the request to capture data repeatedly, so that the preview screen will always show the data
                                mCaptureSession.setRepeatingRequest(mPreviewRequest,
                                        mCaptureCallback, mBackgroundHandler);
                            } catch(CameraAccessException e) { e.printStackTrace(); }}@Override
                        public void onConfigureFailed(
                                @NonNull CameraCaptureSession cameraCaptureSession) {}},null
            );
        } catch(CameraAccessException e) { e.printStackTrace(); }}Copy the code

The templateType parameter in the createCaptureRequest() method represents the request type. There are six types of request types: TEMPLATE_PREVIEW: create a preview request TEMPLATE_STILL_CAPTURE: Create a request suitable for still image capture, image quality over frame rate. TEMPLATE_RECORD: creates a video recording request. TEMPLATE_VIDEO_SNAPSHOT: creates a screen capture request. TEMPLATE_ZERO_SHUTTER_LAG: creates a request for zero shutter delay. Maximize image quality without affecting the preview frame rate. TEMPLATE_MANUAL: Create a basic capture request in which all auto controls are disabled (auto exposure, auto white balance, auto focus).

The createCaptureSession() method takes three parameters: 1.List outputs: the List of surfaces to which we need to output. 2. CameraCaptureSession. StateCallback callback: session state related callback. There can be more than one callback (from different threads), and this Handler is used to distinguish which callback should be called, usually by writing the Handler of the current thread.

About CameraCaptureSession. StateCallback callback method: 1. OnConfigured (@ NonNull CameraCaptureSession session). The camera is configured and ready to process Capture requests. 2.onConfigureFailed(@NonNull CameraCaptureSession session); 3. OnReady (@nonnull CameraCaptureSession Session); The camera is in the ready state and no requests need to be processed.

onActive(@NonNull CameraCaptureSession session); The camera is processing the request. onClosed(@NonNull CameraCaptureSession session); OnSurfacePrepared (@nonnull CameraCaptureSession Session, @nonnull Surface Surface); With these things in mind, creating a preview request is fairly simple.

When you set the preview size information, Surface will associate it with the CaptureRequestBuilder object, and then set the session to begin capturing the scene. The last callback CameraCaptureSession. CaptureCallback we set preview complete logical processing


/** * ImageReader callback object. Still image is ready to be saved. * /
    private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
            = new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader reader) {
            Log.e(TAG, "onImageAvailable:-------------------");
            Image image = reader.acquireLatestImage();
            // We can turn this frame data into a byte array similar to the preview frame data for the Camera1 PreviewCallback callback
            ByteBuffer buffer = image.getPlanes()[0].getBuffer();
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            image.close();
// mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));}};Copy the code

Finally, the method here is to use the real-time frame data in the camera. Here, we can obtain the frame data of still pictures here and perform some layer watermarking. AcquireNextImage method to retrieve still image information and save the image to a local folder. After the basic process is complete, We just need to see how to trigger the photo.


	/** * Lock focus Settings */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void lockFocus(a) {
        try {
            // How to lock the camera (set the camera focus)
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
                    CameraMetadata.CONTROL_AF_TRIGGER_START);
            // mCaptureCallback wait to lock // change the status
            mState = STATE_WAITING_LOCK;
            // Send the focus request
            mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
                    mBackgroundHandler);
        } catch(CameraAccessException e) { e.printStackTrace(); }}Copy the code

By clicking on the event, call camera lock, set mCaptureSession. Capture,


	/** * handle event listener with JPG file capture (preview) */
    private CameraCaptureSession.CaptureCallback mCaptureCallback
            = new CameraCaptureSession.CaptureCallback() {

        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        private void process(CaptureResult result) {
            switch (mState) {
                case STATE_PREVIEW: {
                    // The preview is normal
                    break;
                }
                  // Wait to focus
                case STATE_WAITING_LOCK: { 
                    Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
                    if (afState == null) {
                    	// Failed to focus and directly took the photo
                        captureStillPicture();
                    } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
                            CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
                        Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                        if (aeState == null ||
                                aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
                            mState = STATE_PICTURE_TAKEN;
                            // After focusing, take a photo
                            captureStillPicture();
                        } else{ runPrecaptureSequence(); }}break;
                }
                case STATE_WAITING_PRECAPTURE: {
                    Integer 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: {
                    Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                    if (aeState == null|| aeState ! = CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { mState = STATE_PICTURE_TAKEN; captureStillPicture(); }break;
                }
                default:
                    break; }}@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        @Override
        public void onCaptureProgressed(@NonNull CameraCaptureSession session,
                                        @NonNull CaptureRequest request,
                                        @NonNull CaptureResult partialResult) {
            process(partialResult);
        }

        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        @Override
        public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                       @NonNull CaptureRequest request,
                                       @NonNull TotalCaptureResult result) { process(result); }};Copy the code

	/** * Take still pictures. This method */ should be called when we get a response
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void captureStillPicture(a) {
        try {
            if (null == activity || null == mCameraDevice) {
                return;
            }
            final CaptureRequest.Builder captureBuilder =
                    mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            captureBuilder.addTarget(mImageReader.getSurface());

            // Use the same AE and AF mode as the preview.
            captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                    CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            setAutoFlash(captureBuilder);

            int rotation = getWindowManager().getDefaultDisplay().getRotation();
            // Set the direction
            captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));
			// Create a session
            CameraCaptureSession.CaptureCallback CaptureCallback
                    = new CameraCaptureSession.CaptureCallback() {

                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                               @NonNull CaptureRequest request,
                                               @NonNull TotalCaptureResult result) { Log.e(TAG, mFile.toString()); unlockFocus(); }}; mCaptureSession.stopRepeating(); mCaptureSession.abortCaptures(); mCaptureSession.capture(captureBuilder.build(), CaptureCallback, mBackgroundHandler); }catch(CameraAccessException e) { e.printStackTrace(); }}Copy the code

This article mainly documented the simple process of camera2 custom camera, the code is basically from The Code provided by Google, of course, if you have Kotlin friends, you can also enter the Google Github address at the beginning of the article to view the Kotlin version.