Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a296fece authored by Igor Murashkin's avatar Igor Murashkin
Browse files

camera2: Fix cts test StillCaptureTest#testTakePicture

* Introduce 'fake' metadata for 3A+flash (hardcoded to support nothing)
(will be removed in a later release)

* Open the camera1 device in its own thread, so that the looper it
captures is also our own (and not the main looper)

* Set the picture size based on the size of the JPEG surface outputs

Change-Id: Iaeb5031c6b352115b73d2261a39d65347d75fdc8
parent 3b6e53fc
Loading
Loading
Loading
Loading
+141 −9
Original line number Diff line number Diff line
@@ -17,7 +17,9 @@
package android.hardware.camera2.legacy;

import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
@@ -25,7 +27,9 @@ import android.hardware.camera2.utils.LongParcelable;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
import android.os.ConditionVariable;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;
@@ -33,7 +37,6 @@ import android.view.Surface;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Compatibility implementation of the Camera2 API binder interface.
@@ -53,6 +56,7 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
    private static final String TAG = "CameraDeviceUserShim";

    private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
    private static final int OPEN_CAMERA_TIMEOUT_MS = 5000; // 5 sec (same as api1 cts timeout)

    private final LegacyCameraDevice mLegacyDevice;

@@ -60,29 +64,143 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
    private int mSurfaceIdCounter;
    private boolean mConfiguring;
    private final SparseArray<Surface> mSurfaces;
    private final CameraCharacteristics mCameraCharacteristics;
    private final CameraLooper mCameraInit;

    protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera) {
    protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera,
            CameraCharacteristics characteristics, CameraLooper cameraInit) {
        mLegacyDevice = legacyCamera;
        mConfiguring = false;
        mSurfaces = new SparseArray<Surface>();
        mCameraCharacteristics = characteristics;
        mCameraInit = cameraInit;

        mSurfaceIdCounter = 0;
    }

    /**
     * Create a separate looper/thread for the camera to run on; open the camera.
     *
     * <p>Since the camera automatically latches on to the current thread's looper,
     * it's important that we have our own thread with our own looper to guarantee
     * that the camera callbacks get correctly posted to our own thread.</p>
     */
    private static class CameraLooper implements Runnable, AutoCloseable {
        private final int mCameraId;
        private Looper mLooper;
        private volatile int mInitErrors;
        private final Camera mCamera = Camera.openUninitialized();
        private final ConditionVariable mStartDone = new ConditionVariable();
        private final Thread mThread;

        /**
         * Spin up a new thread, immediately open the camera in the background.
         *
         * <p>Use {@link #waitForOpen} to block until the camera is finished opening.</p>
         *
         * @param cameraId numeric camera Id
         *
         * @see #waitForOpen
         */
        public CameraLooper(int cameraId) {
            mCameraId = cameraId;

            mThread = new Thread(this);
            mThread.start();
        }

        public Camera getCamera() {
            return mCamera;
        }

        @Override
        public void run() {
            // Set up a looper to be used by camera.
            Looper.prepare();

            // Save the looper so that we can terminate this thread
            // after we are done with it.
            mLooper = Looper.myLooper();
            mInitErrors = mCamera.cameraInitUnspecified(mCameraId);

            mStartDone.open();
            Looper.loop();  // Blocks forever until #close is called.
        }

        /**
         * Quit the looper safely; then join until the thread shuts down.
         */
        @Override
        public void close() {
            if (mLooper == null) {
                return;
            }

            mLooper.quitSafely();
            try {
                mThread.join();
            } catch (InterruptedException e) {
                throw new AssertionError(e);
            }

            mLooper = null;
        }

        /**
         * Block until the camera opens; then return its initialization error code (if any).
         *
         * @param timeoutMs timeout in milliseconds
         *
         * @return int error code
         *
         * @throws CameraRuntimeException if the camera open times out with ({@code CAMERA_ERROR})
         */
        public int waitForOpen(int timeoutMs) {
            // Block until the camera is open asynchronously
            if (!mStartDone.block(timeoutMs)) {
                Log.e(TAG, "waitForOpen - Camera failed to open after timeout of "
                        + OPEN_CAMERA_TIMEOUT_MS + " ms");
                try {
                    mCamera.release();
                } catch (RuntimeException e) {
                    Log.e(TAG, "connectBinderShim - Failed to release camera after timeout ", e);
                }

                throw new CameraRuntimeException(CameraAccessException.CAMERA_ERROR);
            }

            return mInitErrors;
        }
    }

    public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks,
                                                         int cameraId) {
        if (DEBUG) {
            Log.d(TAG, "Opening shim Camera device");
        }
        // TODO: Move open/init into LegacyCameraDevice thread when API is switched to async.
        Camera legacyCamera = Camera.openUninitialized();
        int initErrors = legacyCamera.cameraInitUnspecified(cameraId);

        /*
         * Put the camera open on a separate thread with its own looper; otherwise
         * if the main thread is used then the callbacks might never get delivered
         * (e.g. in CTS which run its own default looper only after tests)
         */

        CameraLooper init = new CameraLooper(cameraId);

        // TODO: Make this async instead of blocking
        int initErrors = init.waitForOpen(OPEN_CAMERA_TIMEOUT_MS);
        Camera legacyCamera = init.getCamera();

        // Check errors old HAL initialization
        CameraBinderDecorator.throwOnError(initErrors);

        CameraInfo info = new CameraInfo();
        Camera.getCameraInfo(cameraId, info);

        CameraCharacteristics characteristics =
                LegacyMetadataMapper.createCharacteristics(legacyCamera.getParameters(), info);
        LegacyCameraDevice device = new LegacyCameraDevice(cameraId, legacyCamera, callbacks);
        return new CameraDeviceUserShim(cameraId, device);
        return new CameraDeviceUserShim(cameraId, device, characteristics, init);
    }

    @Override
@@ -90,7 +208,12 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
        if (DEBUG) {
            Log.d(TAG, "disconnect called.");
        }

        try {
            mLegacyDevice.close();
        } finally {
            mCameraInit.close();
        }
    }

    @Override
@@ -218,8 +341,17 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
        if (DEBUG) {
            Log.d(TAG, "createDefaultRequest called.");
        }
        // TODO: implement createDefaultRequest.
        Log.e(TAG, "createDefaultRequest unimplemented.");

        CameraMetadataNative template;
        try {
            template =
                    LegacyMetadataMapper.createRequestTemplate(mCameraCharacteristics, templateId);
        } catch (IllegalArgumentException e) {
            Log.e(TAG, "createDefaultRequest - invalid templateId specified");
            return CameraBinderDecorator.BAD_VALUE;
        }

        request.swap(template);
        return CameraBinderDecorator.NO_ERROR;
    }

+21 −1
Original line number Diff line number Diff line
@@ -29,12 +29,14 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.RemoteException;
import android.util.Log;
import android.util.Size;
import android.view.Surface;

import java.util.ArrayList;
import java.util.List;

import static android.hardware.camera2.utils.CameraBinderDecorator.*;
import static com.android.internal.util.Preconditions.*;

/**
 * This class emulates the functionality of a Camera2 device using a the old Camera class.
@@ -367,9 +369,27 @@ public class LegacyCameraDevice implements AutoCloseable {
        }
    }

    /**
     * Query the surface for its currently configured default buffer size.
     * @param surface a non-{@code null} {@code Surface}
     * @return the width and height of the surface
     *
     * @throws NullPointerException if the {@code surface} was {@code null}
     * @throws IllegalStateException if the {@code surface} was invalid
     */
    static Size getSurfaceSize(Surface surface) {
        checkNotNull(surface);

        int[] dimens = new int[2];
        nativeDetectSurfaceDimens(surface, /*out*/dimens);

        return new Size(dimens[0], dimens[1]);
    }

    protected static native int nativeDetectSurfaceType(Surface surface);

    protected static native void nativeDetectSurfaceDimens(Surface surface, int[] dimens);
    protected static native void nativeDetectSurfaceDimens(Surface surface,
            /*out*/int[/*2*/] dimens);

    protected static native void nativeConfigureSurface(Surface surface, int width, int height,
                                                        int pixelFormat);
+275 −3
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.Size;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.impl.CameraMetadataNative;
@@ -32,6 +34,7 @@ import android.util.Range;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import static com.android.internal.util.Preconditions.*;
import static android.hardware.camera2.CameraCharacteristics.*;
@@ -56,6 +59,42 @@ public class LegacyMetadataMapper {
    private static final long APPROXIMATE_JPEG_ENCODE_TIME = 600; // ms
    private static final long NS_PER_MS = 1000000;

    /*
     * Development hijinks: Lie about not supporting certain capabilities
     *
     * - Unblock some CTS tests from running whose main intent is not the metadata itself
     *
     * TODO: Remove these constants and strip out any code that previously relied on them
     * being set to true.
     */
    private static final boolean LIE_ABOUT_FLASH = true;
    private static final boolean LIE_ABOUT_AE = true;
    private static final boolean LIE_ABOUT_AF = true;
    private static final boolean LIE_ABOUT_AWB = true;

    /**
     * Create characteristics for a legacy device by mapping the {@code parameters}
     * and {@code info}
     *
     * @param parameters A non-{@code null} parameters set
     * @param info Camera info with camera facing direction and angle of orientation
     *
     * @return static camera characteristics for a camera device
     *
     * @throws NullPointerException if any of the args were {@code null}
     */
    public static CameraCharacteristics createCharacteristics(Camera.Parameters parameters,
            CameraInfo info) {
        checkNotNull(parameters, "parameters must not be null");
        checkNotNull(info, "info must not be null");

        String paramStr = parameters.flatten();
        android.hardware.CameraInfo outerInfo = new android.hardware.CameraInfo();
        outerInfo.info = info;

        return createCharacteristics(paramStr, outerInfo);
    }

    /**
     * Create characteristics for a legacy device by mapping the {@code parameters}
     * and {@code info}
@@ -99,7 +138,8 @@ public class LegacyMetadataMapper {
    private static void mapCameraParameters(CameraMetadataNative m, Camera.Parameters p) {
        m.set(INFO_SUPPORTED_HARDWARE_LEVEL, INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED);
        mapStreamConfigs(m, p);
        mapAeConfig(m, p);
        mapControlAe(m, p);
        mapControlAwb(m, p);
        mapCapabilities(m, p);
        mapLens(m, p);
        mapFlash(m, p);
@@ -164,8 +204,10 @@ public class LegacyMetadataMapper {
    }

    @SuppressWarnings({"unchecked"})
    private static void mapAeConfig(CameraMetadataNative m, Camera.Parameters p) {

    private static void mapControlAe(CameraMetadataNative m, Camera.Parameters p) {
        /*
         * control.aeAvailableTargetFpsRanges
         */
        List<int[]> fpsRanges = p.getSupportedPreviewFpsRange();
        if (fpsRanges == null) {
            throw new AssertionError("Supported FPS ranges cannot be null.");
@@ -182,6 +224,10 @@ public class LegacyMetadataMapper {
        }
        m.set(CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES, ranges);

        /*
         * control.aeAvailableAntiBandingModes
         */

        List<String> antiBandingModes = p.getSupportedAntibanding();
        int antiBandingModesSize = antiBandingModes.size();
        if (antiBandingModesSize > 0) {
@@ -198,6 +244,49 @@ public class LegacyMetadataMapper {
            }
            m.set(CONTROL_AE_AVAILABLE_ANTIBANDING_MODES, Arrays.copyOf(modes, j));
        }

        /*
         * control.aeAvailableModes
         */
        List<String> flashModes = p.getSupportedFlashModes();

        String[] flashModeStrings = new String[] {
                Camera.Parameters.FLASH_MODE_AUTO,
                Camera.Parameters.FLASH_MODE_ON,
                Camera.Parameters.FLASH_MODE_RED_EYE,
                // Map these manually
                Camera.Parameters.FLASH_MODE_TORCH,
                Camera.Parameters.FLASH_MODE_OFF,
        };
        int[] flashModeInts = new int[] {
                CONTROL_AE_MODE_ON,
                CONTROL_AE_MODE_ON_AUTO_FLASH,
                CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE
        };
        int[] aeAvail = convertStringListToIntArray(flashModes, flashModeStrings, flashModeInts);

        // No flash control -> AE is always on
        if (aeAvail == null || aeAvail.length == 0) {
            aeAvail = new int[] {
                    CONTROL_AE_MODE_ON
            };
        }

        if (LIE_ABOUT_FLASH) {
            // TODO: Remove this branch
            Log.w(TAG, "mapControlAe - lying; saying we only support CONTROL_AE_MODE_ON");
            aeAvail = new int[] {
                    CONTROL_AE_MODE_ON
            };
        }

        m.set(CONTROL_AE_AVAILABLE_MODES, aeAvail);
    }

    private static void mapControlAwb(CameraMetadataNative m, Camera.Parameters p) {
        if (!LIE_ABOUT_AWB) {
            throw new AssertionError("Not implemented yet");
        }
    }

    private static void mapCapabilities(CameraMetadataNative m, Camera.Parameters p) {
@@ -226,6 +315,12 @@ public class LegacyMetadataMapper {
            }
        }

        if (LIE_ABOUT_FLASH && flashAvailable) {
            // TODO: remove this branch
            Log.w(TAG, "mapFlash - lying; saying we never support flash");
            flashAvailable = false;
        }

        m.set(FLASH_INFO_AVAILABLE, flashAvailable);
    }

@@ -320,7 +415,52 @@ public class LegacyMetadataMapper {
                                                      CaptureRequest request,
                                                      long timestamp) {
        CameraMetadataNative result = new CameraMetadataNative();

        /*
         * control
         */
        // control.afState
        if (LIE_ABOUT_AF) {
            // TODO: Implement autofocus state machine
            result.set(CaptureResult.CONTROL_AF_MODE, request.get(CaptureRequest.CONTROL_AF_MODE));
        }

        // control.aeState
        if (LIE_ABOUT_AE) {
            // Lie to pass CTS temporarily.
            // TODO: Implement precapture trigger, after which we can report CONVERGED ourselves
            result.set(CaptureResult.CONTROL_AE_STATE,
                    CONTROL_AE_STATE_CONVERGED);

            result.set(CaptureResult.CONTROL_AE_MODE,
                    request.get(CaptureRequest.CONTROL_AE_MODE));
        }

        // control.awbLock
        result.set(CaptureResult.CONTROL_AWB_LOCK, params.getAutoWhiteBalanceLock());

        // control.awbState
        if (LIE_ABOUT_AWB) {
            // Lie to pass CTS temporarily.
            // TODO: CTS needs to be updated not to query this value
            // for LIMITED devices unless its guaranteed to be available.
            result.set(CaptureResult.CONTROL_AWB_STATE,
                    CameraMetadata.CONTROL_AWB_STATE_CONVERGED);
            // TODO: Read the awb mode from parameters instead
            result.set(CaptureResult.CONTROL_AWB_MODE,
                    request.get(CaptureRequest.CONTROL_AWB_MODE));
        }

        /*
         * lens
         */
        // lens.focalLength
        result.set(CaptureResult.LENS_FOCAL_LENGTH, params.getFocalLength());

        /*
         * sensor
         */
        // sensor.timestamp
        result.set(CaptureResult.SENSOR_TIMESTAMP, timestamp);

        // TODO: Remaining result metadata tags conversions.
@@ -335,17 +475,149 @@ public class LegacyMetadataMapper {
     */
    public static void convertRequestMetadata(CaptureRequest request,
            /*out*/Camera.Parameters params) {

        /*
         * control.ae*
         */
        // control.aeAntibandingMode
        Integer antiBandingMode = request.get(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE);
        if (antiBandingMode != null) {
            String legacyMode = convertAntiBandingModeToLegacy(antiBandingMode);
            if (legacyMode != null) params.setAntibanding(legacyMode);
        }

        // control.aeTargetFpsRange
        Range<Integer> aeFpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
        if (aeFpsRange != null) {
            int[] legacyFps = convertAeFpsRangeToLegacy(aeFpsRange);
            params.setPreviewFpsRange(legacyFps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
                    legacyFps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
        }

        /*
         * control
         */
        // control.awbLock
        Boolean awbLock = request.get(CaptureRequest.CONTROL_AWB_LOCK);
        params.setAutoWhiteBalanceLock(awbLock == null ? false : awbLock);
    }

    /**
     * Create an int[] from the List<> by using {@code convertFrom} and {@code convertTo}
     * as a one-to-one map (via the index).
     *
     * <p>Strings not appearing in {@code convertFrom} are ignored (with a warning);
     * strings appearing in {@code convertFrom} but not {@code convertTo} are silently
     * dropped.</p>
     *
     * @param list Source list of strings
     * @param convertFrom Conversion list of strings
     * @param convertTo Conversion list of ints
     * @return An array of ints where the values correspond to the ones in {@code convertTo}
     *         or {@code null} if {@code list} was {@code null}
     */
    private static int[] convertStringListToIntArray(
            List<String> list, String[] convertFrom, int[] convertTo) {
        if (list == null) {
            return null;
        }

        List<Integer> convertedList = new ArrayList<>(list.size());

        for (String str : list) {
            int strIndex = getArrayIndex(convertFrom, str);

            // Guard against bad API1 values
            if (strIndex < 0) {
                Log.w(TAG, "Ignoring invalid parameter " + str);
                continue;
            }

            // Ignore values we can't map into (intentional)
            if (strIndex < convertTo.length) {
                convertedList.add(convertTo[strIndex]);
            }
        }

        int[] returnArray = new int[convertedList.size()];
        for (int i = 0; i < returnArray.length; ++i) {
            returnArray[i] = convertedList.get(i);
        }

        return returnArray;
    }

    /** Return the index of {@code needle} in the {@code array}, or else {@code -1} */
    private static <T> int getArrayIndex(T[] array, T needle) {
        if (needle == null) {
            return -1;
        }

        int index = 0;
        for (T elem : array) {
            if (Objects.equals(elem, needle)) {
                return index;
            }
            index++;
        }

        return -1;
    }

    /**
     * Create a request template
     *
     * @param c a non-{@code null} camera characteristics for this camera
     * @param templateId a non-negative template ID
     *
     * @return a non-{@code null} request template
     *
     * @throws IllegalArgumentException if {@code templateId} was invalid
     *
     * @see android.hardware.camera2.CameraDevice#TEMPLATE_MANUAL
     */
    public static CameraMetadataNative createRequestTemplate(
            CameraCharacteristics c, int templateId) {
        if (templateId < 0 || templateId > CameraDevice.TEMPLATE_MANUAL) {
            throw new IllegalArgumentException("templateId out of range");
        }

        CameraMetadataNative m = new CameraMetadataNative();

        /*
         * NOTE: If adding new code here and it needs to query the static info,
         * query the camera characteristics, so we can reuse this for api2 code later
         * to create our own templates in the framework
         */

        if (LIE_ABOUT_AWB) {
            m.set(CaptureRequest.CONTROL_AWB_MODE, CameraMetadata.CONTROL_AWB_MODE_AUTO);
        } else {
            throw new AssertionError("Valid control.awbMode not implemented yet");
        }

        // control.aeMode
        m.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_ON);
        // AE is always unconditionally available in API1 devices

        // control.afMode
        {
            Float minimumFocusDistance = c.get(LENS_INFO_MINIMUM_FOCUS_DISTANCE);

            int afMode;
            if (minimumFocusDistance != null &&
                    minimumFocusDistance == LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS) {
                // Cannot control auto-focus with fixed-focus cameras
                afMode = CameraMetadata.CONTROL_AF_MODE_OFF;
            } else {
                // If a minimum focus distance is reported; the camera must have AF
                afMode = CameraMetadata.CONTROL_AF_MODE_AUTO;
            }

            m.set(CaptureRequest.CONTROL_AF_MODE, afMode);
        }

        // TODO: map other request template values
        return m;
    }
}
+112 −4

File changed.

Preview size limit exceeded, changes collapsed.