public final class

Camera2CameraControl

extends java.lang.Object

implements CameraControlInternal

 java.lang.Object

↳androidx.camera.camera2.impl.Camera2CameraControl

Overview

A Camera2 implementation for CameraControlInternal interface

Summary

Constructors
publicCamera2CameraControl(CameraCharacteristics cameraCharacteristics, CameraControlInternal.ControlUpdateListener controlUpdateListener, java.util.concurrent.ScheduledExecutorService scheduler, java.util.concurrent.Executor executor)

Methods
public voidcancelAfAeTrigger(boolean cancelAfTrigger, boolean cancelAePrecaptureTrigger)

Issues CaptureRequest or CaptureRequest request to cancel auto focus or auto exposure scan.

public voidcancelFocusAndMetering()

public voidenableTorch(boolean torch)

public FlashModegetFlashMode()

public booleanisTorchOn()

public voidsetCropRegion(Rect crop)

public voidsetFlashMode(FlashMode flashMode)

public voidsetPreviewAspectRatio(Rational previewAspectRatio)

public voidstartFocusAndMetering(FocusMeteringAction action)

public voidsubmitCaptureRequests(java.util.List<CaptureConfig> captureConfigs)

public voidtriggerAePrecapture()

Issues a CaptureRequest request to start auto exposure scan.

public voidtriggerAf()

Issues a CaptureRequest request to start auto focus scan.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public Camera2CameraControl(CameraCharacteristics cameraCharacteristics, CameraControlInternal.ControlUpdateListener controlUpdateListener, java.util.concurrent.ScheduledExecutorService scheduler, java.util.concurrent.Executor executor)

Methods

public void setPreviewAspectRatio(Rational previewAspectRatio)

public void startFocusAndMetering(FocusMeteringAction action)

public void cancelFocusAndMetering()

public void setCropRegion(Rect crop)

public FlashMode getFlashMode()

public void setFlashMode(FlashMode flashMode)

public void enableTorch(boolean torch)

public boolean isTorchOn()

public void triggerAf()

Issues a CaptureRequest request to start auto focus scan.

public void triggerAePrecapture()

Issues a CaptureRequest request to start auto exposure scan.

public void cancelAfAeTrigger(boolean cancelAfTrigger, boolean cancelAePrecaptureTrigger)

Issues CaptureRequest or CaptureRequest request to cancel auto focus or auto exposure scan.

public void submitCaptureRequests(java.util.List<CaptureConfig> captureConfigs)

Source

/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.camera.camera2.impl;

import android.graphics.Rect;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.util.Rational;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import androidx.camera.camera2.Camera2Config;
import androidx.camera.core.CameraControlInternal;
import androidx.camera.core.CaptureConfig;
import androidx.camera.core.Config;
import androidx.camera.core.FlashMode;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.SessionConfig;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;

/**
 * A Camera2 implementation for CameraControlInternal interface
 *
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public final class Camera2CameraControl implements CameraControlInternal {
    private static final String TAG = "Camera2CameraControl";
    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
    final CameraControlSessionCallback mSessionCallback;
    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
    final Executor mExecutor;
    private final CameraCharacteristics mCameraCharacteristics;
    private final ControlUpdateListener mControlUpdateListener;
    private final ScheduledExecutorService mScheduler;
    private final SessionConfig.Builder mSessionConfigBuilder = new SessionConfig.Builder();
    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
    volatile Rational mPreviewAspectRatio = null;
    @VisibleForTesting
    final FocusMeteringControl mFocusMeteringControl;
    // use volatile modifier to make these variables in sync in all threads.
    private volatile boolean mIsTorchOn = false;
    private volatile FlashMode mFlashMode = FlashMode.OFF;

    //******************** Should only be accessed by executor *****************************//
    private Rect mCropRect = null;
    //**************************************************************************************//


    public Camera2CameraControl(@NonNull CameraCharacteristics cameraCharacteristics,
            @NonNull ControlUpdateListener controlUpdateListener,
            @NonNull ScheduledExecutorService scheduler, @NonNull Executor executor) {
        mCameraCharacteristics = cameraCharacteristics;
        mControlUpdateListener = controlUpdateListener;
        if (CameraXExecutors.isSequentialExecutor(executor)) {
            mExecutor = executor;
        } else {
            mExecutor = CameraXExecutors.newSequentialExecutor(executor);
        }
        mScheduler = scheduler;
        mSessionCallback = new CameraControlSessionCallback(mExecutor);
        mSessionConfigBuilder.setTemplateType(getDefaultTemplate());
        mSessionConfigBuilder.addRepeatingCameraCaptureCallback(
                CaptureCallbackContainer.create(mSessionCallback));

        mFocusMeteringControl = new FocusMeteringControl(this, mExecutor, mScheduler);

        // Initialize the session config
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                updateSessionConfig();
            }
        });
    }

    public void setPreviewAspectRatio(@Nullable Rational previewAspectRatio) {
        mPreviewAspectRatio = previewAspectRatio;
    }

    @Override
    public void startFocusAndMetering(@NonNull FocusMeteringAction action) {
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio);
            }
        });
    }

    @Override
    public void cancelFocusAndMetering() {
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                mFocusMeteringControl.cancelFocusAndMetering();
            }
        });
    }

    /** {@inheritDoc} */
    @Override
    public void setCropRegion(@Nullable final Rect crop) {
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                setCropRegionInternal(crop);
            }
        });
    }

    @NonNull
    @Override
    public FlashMode getFlashMode() {
        return mFlashMode;
    }

    /** {@inheritDoc} */
    @Override
    public void setFlashMode(@NonNull FlashMode flashMode) {
        // update mFlashMode immediately so that following getFlashMode() returns correct value.
        mFlashMode = flashMode;

        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                updateSessionConfig();
            }
        });
    }

    /** {@inheritDoc} */
    @Override
    public void enableTorch(final boolean torch) {
        // update isTorchOn immediately so that following isTorchOn() returns correct value.
        mIsTorchOn = torch;

        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                enableTorchInternal(torch);
            }
        });

    }

    /** {@inheritDoc} */
    @Override
    public boolean isTorchOn() {
        return mIsTorchOn;
    }

    /**
     * Issues a {@link CaptureRequest#CONTROL_AF_TRIGGER_START} request to start auto focus scan.
     */
    @Override
    public void triggerAf() {
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                mFocusMeteringControl.triggerAf();
            }
        });
    }

    /**
     * Issues a {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER_START} request to start auto
     * exposure scan.
     */
    @Override
    public void triggerAePrecapture() {
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                mFocusMeteringControl.triggerAePrecapture();
            }
        });
    }

    /**
     * Issues {@link CaptureRequest#CONTROL_AF_TRIGGER_CANCEL} or {@link
     * CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL} request to cancel auto focus or auto
     * exposure scan.
     */
    @Override
    public void cancelAfAeTrigger(final boolean cancelAfTrigger,
            final boolean cancelAePrecaptureTrigger) {
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                mFocusMeteringControl.cancelAfAeTrigger(cancelAfTrigger,
                        cancelAePrecaptureTrigger);
            }
        });
    }

    /** {@inheritDoc} */
    @Override
    public void submitCaptureRequests(@NonNull final List<CaptureConfig> captureConfigs) {
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                submitCaptureRequestsInternal(captureConfigs);
            }
        });
    }

    @WorkerThread
    private int getDefaultTemplate() {
        return CameraDevice.TEMPLATE_PREVIEW;
    }

    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
    @WorkerThread
    void updateSessionConfig() {
        mSessionConfigBuilder.setImplementationOptions(getSessionOptions());
        mControlUpdateListener.onCameraControlUpdateSessionConfig(mSessionConfigBuilder.build());
    }

    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
    @WorkerThread
    void setCropRegionInternal(final Rect crop) {
        mCropRect = crop;
        updateSessionConfig();
    }

    @WorkerThread
    @NonNull
    Rect getCropSensorRegion() {
        Rect cropRect = mCropRect;
        if (cropRect == null) {
            cropRect = getSensorRect();
        }
        return cropRect;
    }

    @WorkerThread
    @NonNull
    Rect getSensorRect() {
        return mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
    }

    @WorkerThread
    void removeCaptureResultListener(@NonNull CaptureResultListener listener) {
        mSessionCallback.removeListener(listener);
    }

    @WorkerThread
    void addCaptureResultListener(@NonNull CaptureResultListener listener) {
        mSessionCallback.addListener(listener);
    }

    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
    @WorkerThread
    void enableTorchInternal(boolean torch) {
        if (!torch) {
            // Send capture request with AE_MODE_ON + FLASH_MODE_OFF to turn off torch.
            CaptureConfig.Builder singleRequestBuilder = new CaptureConfig.Builder();
            singleRequestBuilder.setTemplateType(getDefaultTemplate());
            singleRequestBuilder.setUseRepeatingSurface(true);
            Camera2Config.Builder configBuilder = new Camera2Config.Builder();
            configBuilder.setCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE,
                    getSupportedAeMode(CaptureRequest.CONTROL_AE_MODE_ON));
            configBuilder.setCaptureRequestOption(CaptureRequest.FLASH_MODE,
                    CaptureRequest.FLASH_MODE_OFF);
            singleRequestBuilder.addImplementationOptions(configBuilder.build());
            submitCaptureRequestsInternal(Collections.singletonList(singleRequestBuilder.build()));
        }
        updateSessionConfig();
    }


    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
    @WorkerThread
    void submitCaptureRequestsInternal(final List<CaptureConfig> captureConfigs) {
        mControlUpdateListener.onCameraControlCaptureRequests(captureConfigs);
    }

    /**
     * Gets session options by current status.
     *
     * <p>The session options are based on the current torch status, flash mode, focus area, crop
     * area, etc... They should be appended to the repeat request.
     */
    @VisibleForTesting
    @WorkerThread
    Config getSessionOptions() {
        Camera2Config.Builder builder = new Camera2Config.Builder();
        builder.setCaptureRequestOption(
                CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);

        // AF Mode is assigned in mFocusMeteringControl.
        mFocusMeteringControl.addFocusMeteringOptions(builder);

        int aeMode = CaptureRequest.CONTROL_AE_MODE_ON;
        if (mIsTorchOn) {
            builder.setCaptureRequestOption(CaptureRequest.FLASH_MODE,
                    CaptureRequest.FLASH_MODE_TORCH);
        } else {
            switch (mFlashMode) {
                case OFF:
                    aeMode = CaptureRequest.CONTROL_AE_MODE_ON;
                    break;
                case ON:
                    aeMode = CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH;
                    break;
                case AUTO:
                    aeMode = CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH;
                    break;
            }
        }
        builder.setCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE, getSupportedAeMode(aeMode));

        builder.setCaptureRequestOption(
                CaptureRequest.CONTROL_AWB_MODE,
                getSupportedAwbMode(CaptureRequest.CONTROL_AWB_MODE_AUTO));

        if (mCropRect != null) {
            builder.setCaptureRequestOption(CaptureRequest.SCALER_CROP_REGION, mCropRect);
        }

        return builder.build();
    }

    /**
     * Returns a supported AF mode which will be preferredMode if it is supported.
     *
     * <p><pre>If preferredMode is not supported, fallback with the following priority (highest to
     * lowest).
     * 1) {@link CaptureRequest#CONTROL_AF_MODE_CONTINUOUS_PICTURE}
     * 2) {@link CaptureRequest#CONTROL_AF_MODE_AUTO)}
     * 3) {@link CaptureRequest#CONTROL_AF_MODE_OFF}
     * </pre>
     */
    @WorkerThread
    int getSupportedAfMode(int preferredMode) {
        int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
        if (modes == null) {
            return CaptureRequest.CONTROL_AF_MODE_OFF;
        }

        // if preferredMode is supported, use it
        if (isModeInList(preferredMode, modes)) {
            return preferredMode;
        }

        // if not found, priority is CONTINUOUS_PICTURE > AUTO > OFF
        if (isModeInList(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE, modes)) {
            return CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
        } else if (isModeInList(CaptureRequest.CONTROL_AF_MODE_AUTO, modes)) {
            return CaptureRequest.CONTROL_AF_MODE_AUTO;
        }

        return CaptureRequest.CONTROL_AF_MODE_OFF;
    }

    /**
     * Returns a supported AE mode which will be preferredMode if it is supported.
     *
     * <p><pre>If preferredMode is not supported, fallback with the following priority (highest to
     * lowest).
     * 1) {@link CaptureRequest#CONTROL_AE_MODE_ON}
     * 2) {@link CaptureRequest#CONTROL_AE_MODE_OFF)}
     * </pre>
     */
    @WorkerThread
    private int getSupportedAeMode(int preferredMode) {
        int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES);

        if (modes == null) {
            return CaptureRequest.CONTROL_AE_MODE_OFF;
        }

        // if preferredMode is supported, use it
        if (isModeInList(preferredMode, modes)) {
            return preferredMode;
        }

        // if not found, priority is AE_ON > AE_OFF
        if (isModeInList(CaptureRequest.CONTROL_AE_MODE_ON, modes)) {
            return CaptureRequest.CONTROL_AE_MODE_ON;
        }

        return CaptureRequest.CONTROL_AE_MODE_OFF;
    }

    /**
     * Returns a supported AWB mode which will be preferredMode if it is supported.
     *
     * <p><pre>If preferredMode is not supported, fallback with the following priority (highest to
     * lowest).
     * 1) {@link CaptureRequest#CONTROL_AWB_MODE_AUTO}
     * 2) {@link CaptureRequest#CONTROL_AWB_MODE_OFF)}
     * </pre>
     */
    @WorkerThread
    private int getSupportedAwbMode(int preferredMode) {
        int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES);

        if (modes == null) {
            return CaptureRequest.CONTROL_AWB_MODE_OFF;
        }

        // if preferredMode is supported, use it
        if (isModeInList(preferredMode, modes)) {
            return preferredMode;
        }

        // if not found, priority is AWB_AUTO > AWB_OFF
        if (isModeInList(CaptureRequest.CONTROL_AWB_MODE_AUTO, modes)) {
            return CaptureRequest.CONTROL_AWB_MODE_AUTO;
        }

        return CaptureRequest.CONTROL_AWB_MODE_OFF;
    }

    @WorkerThread
    private boolean isModeInList(int mode, int[] modeList) {
        for (int m : modeList) {
            if (mode == m) {
                return true;
            }
        }
        return false;
    }

    /** An interface to listen to camera capture results. */
    interface CaptureResultListener {
        /**
         * Callback to handle camera capture results.
         *
         * @param captureResult camera capture result.
         * @return true to finish listening, false to continue listening.
         */
        boolean onCaptureResult(@NonNull TotalCaptureResult captureResult);
    }

    static final class CameraControlSessionCallback extends CaptureCallback {

        /* synthetic accessor */final Set<CaptureResultListener> mResultListeners = new HashSet<>();
        private final Executor mExecutor;

        CameraControlSessionCallback(@NonNull Executor executor) {
            mExecutor = executor;
        }

        @WorkerThread
        void addListener(@NonNull CaptureResultListener listener) {
            mResultListeners.add(listener);
        }

        @WorkerThread
        void removeListener(@NonNull CaptureResultListener listener) {
            mResultListeners.remove(listener);
        }

        @Override
        public void onCaptureCompleted(
                @NonNull CameraCaptureSession session,
                @NonNull CaptureRequest request,
                @NonNull final TotalCaptureResult result) {

            mExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    Set<CaptureResultListener> removeSet = new HashSet<>();
                    for (CaptureResultListener listener : mResultListeners) {
                        boolean isFinished = listener.onCaptureResult(result);
                        if (isFinished) {
                            removeSet.add(listener);
                        }
                    }

                    if (!removeSet.isEmpty()) {
                        mResultListeners.removeAll(removeSet);
                    }
                }
            });
        }
    }
}