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

Commit 83d8639e authored by Igor Murashkin's avatar Igor Murashkin
Browse files

camera2: legacy: Add focus support

* Characteristics will list control.availableAfModes
* Request/result for control.afMode and control.afState

(Does not yet support control.afRegions)

Change-Id: I828111425fa587114d5159f7fb2b1e53a2c74e61
parent f83fdd08
Loading
Loading
Loading
Loading
+58 −8
Original line number Diff line number Diff line
@@ -15,11 +15,12 @@
 */
package android.hardware.camera2.legacy;

import android.hardware.camera2.impl.CameraMetadataNative;
import android.util.Log;
import android.util.MutableLong;
import android.util.Pair;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@@ -77,7 +78,7 @@ public class CaptureCollector {
                if (needsPreview && isPreviewCompleted()) {
                    CaptureCollector.this.onPreviewCompleted();
                }
                CaptureCollector.this.onRequestCompleted(mRequest, mLegacy, mTimestamp);
                CaptureCollector.this.onRequestCompleted(this);
            }
        }

@@ -176,13 +177,13 @@ public class CaptureCollector {
    private final ArrayDeque<CaptureHolder> mJpegProduceQueue;
    private final ArrayDeque<CaptureHolder> mPreviewCaptureQueue;
    private final ArrayDeque<CaptureHolder> mPreviewProduceQueue;
    private final ArrayList<CaptureHolder> mCompletedRequests = new ArrayList<>();

    private final ReentrantLock mLock = new ReentrantLock();
    private final Condition mIsEmpty;
    private final Condition mPreviewsEmpty;
    private final Condition mNotFull;
    private final CameraDeviceState mDeviceState;
    private final LegacyResultMapper mMapper = new LegacyResultMapper();
    private int mInFlight = 0;
    private int mInFlightPreviews = 0;
    private final int mMaxInFlight;
@@ -319,6 +320,54 @@ public class CaptureCollector {
        }
    }

    /**
     * Wait for the specified request to be completed (all buffers available).
     *
     * <p>May not wait for the same request more than once, since a successful wait
     * will erase the history of that request.</p>
     *
     * @param holder the {@link RequestHolder} for this request.
     * @param timeout a timeout to use for this call.
     * @param unit the units to use for the timeout.
     * @param timestamp the timestamp of the request will be written out to here, in ns
     *
     * @return {@code false} if this method timed out.
     *
     * @throws InterruptedException if this thread is interrupted.
     */
    public boolean waitForRequestCompleted(RequestHolder holder, long timeout, TimeUnit unit,
            MutableLong timestamp)
            throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.mLock;
        lock.lock();
        try {
            while (!removeRequestIfCompleted(holder, /*out*/timestamp)) {
                if (nanos <= 0) {
                    return false;
                }
                nanos = mNotFull.awaitNanos(nanos);
            }
            return true;
        } finally {
            lock.unlock();
        }
    }

    private boolean removeRequestIfCompleted(RequestHolder holder, MutableLong timestamp) {
        int i = 0;
        for (CaptureHolder h : mCompletedRequests) {
            if (h.mRequest.equals(holder)) {
                timestamp.value = h.mTimestamp;
                mCompletedRequests.remove(i);
                return true;
            }
            i++;
        }

        return false;
    }

    /**
     * Called to alert the {@link CaptureCollector} that the jpeg capture has begun.
     *
@@ -431,8 +480,9 @@ public class CaptureCollector {
        }
    }

    private void onRequestCompleted(RequestHolder request, LegacyRequest legacyHolder,
                                    long timestamp) {
    private void onRequestCompleted(CaptureHolder capture) {
        RequestHolder request = capture.mRequest;

        mInFlight--;
        if (DEBUG) {
            Log.d(TAG, "Completed request " + request.getRequestId() +
@@ -442,12 +492,12 @@ public class CaptureCollector {
            throw new IllegalStateException(
                    "More captures completed than requests queued.");
        }

        mCompletedRequests.add(capture);

        mNotFull.signalAll();
        if (mInFlight == 0) {
            mIsEmpty.signalAll();
        }
        CameraMetadataNative result = mMapper.cachedConvertResultMetadata(
                legacyHolder, timestamp);
        mDeviceState.setCaptureResult(request, result);
    }
}
+282 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 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 android.hardware.camera2.legacy;

import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.utils.ParamsUtils;
import android.util.Log;

import java.util.Objects;

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

/**
 * Map capture request data into legacy focus state transitions.
 *
 * <p>This object will asynchronously process auto-focus changes, so no interaction
 * with it is necessary beyond reading the current state and updating with the latest trigger.</p>
 */
@SuppressWarnings("deprecation")
public class LegacyFocusStateMapper {
    private static String TAG = "LegacyFocusStateMapper";
    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);

    private final Camera mCamera;

    private int mAfStatePrevious = CONTROL_AF_STATE_INACTIVE;
    private String mAfModePrevious = null;

    /** Guard mAfRun and mAfState */
    private final Object mLock = new Object();
    /** Guard access with mLock */
    private int mAfRun = 0;
    /** Guard access with mLock */
    private int mAfState = CONTROL_AF_STATE_INACTIVE;

    /**
     * Instantiate a new focus state mapper.
     *
     * @param camera a non-{@code null} camera1 device
     *
     * @throws NullPointerException if any of the args were {@code null}
     */
    public LegacyFocusStateMapper(Camera camera) {
        mCamera = checkNotNull(camera, "camera must not be null");
    }

    /**
     * Process the AF triggers from the request as a camera1 autofocus routine.
     *
     * <p>This method should be called after the parameters are {@link LegacyRequestMapper mapped}
     * with the request.</p>
     *
     * <p>Callbacks are processed in the background, and the next call to {@link #mapResultTriggers}
     * will have the latest AF state as reflected by the camera1 callbacks.</p>
     *
     * <p>None of the arguments will be mutated.</p>
     *
     * @param captureRequest a non-{@code null} request
     * @param parameters a non-{@code null} parameters corresponding to this request (read-only)
     */
    public void processRequestTriggers(CaptureRequest captureRequest,
            Camera.Parameters parameters) {
        checkNotNull(captureRequest, "captureRequest must not be null");

        /*
         * control.afTrigger
         */
        int afTrigger = ParamsUtils.getOrDefault(captureRequest, CONTROL_AF_TRIGGER,
                CONTROL_AF_TRIGGER_IDLE);

        final String afMode = parameters.getFocusMode();

        if (!Objects.equals(mAfModePrevious, afMode)) {
            if (VERBOSE) {
                Log.v(TAG, "processRequestTriggers - AF mode switched from " + mAfModePrevious +
                        " to " + afMode);
            }

            // Switching modes always goes back to INACTIVE; ignore callbacks from previous modes

            synchronized (mLock) {
                ++mAfRun;
                mAfState = CONTROL_AF_STATE_INACTIVE;
            }
        }

        mAfModePrevious = afMode;

        // Passive AF Scanning
        {
            final int currentAfRun;

            synchronized (mLock) {
                currentAfRun = mAfRun;
            }

            mCamera.setAutoFocusMoveCallback(new Camera.AutoFocusMoveCallback() {
                @Override
                public void onAutoFocusMoving(boolean start, Camera camera) {
                    synchronized (mLock) {
                        int latestAfRun = mAfRun;

                        if (VERBOSE) {
                            Log.v(TAG, "onAutoFocusMoving - start " + start + " latest AF run " +
                                    latestAfRun + ", last AF run " + currentAfRun);
                        }

                        if (currentAfRun != latestAfRun) {
                            Log.d(TAG,
                                    "onAutoFocusMoving - ignoring move callbacks from old af run"
                                            + currentAfRun);
                            return;
                        }

                        int newAfState = start ?
                                CONTROL_AF_STATE_PASSIVE_SCAN :
                                CONTROL_AF_STATE_PASSIVE_FOCUSED;
                        // We never send CONTROL_AF_STATE_PASSIVE_UNFOCUSED

                        switch (afMode) {
                            case Parameters.FOCUS_MODE_CONTINUOUS_PICTURE:
                            case Parameters.FOCUS_MODE_CONTINUOUS_VIDEO:
                                break;
                            // This callback should never be sent in any other AF mode
                            default:
                                Log.w(TAG, "onAutoFocus - got unexpected onAutoFocus in mode "
                                        + afMode);

                        }

                        mAfState = newAfState;
                    }
                }
            });
        }

        // AF Locking
        switch (afTrigger) {
            case CONTROL_AF_TRIGGER_START:
                final int currentAfRun;
                synchronized (mLock) {
                    currentAfRun = ++mAfRun;
                    mAfState = CONTROL_AF_STATE_ACTIVE_SCAN;
                }

                if (VERBOSE) {
                    Log.v(TAG, "processRequestTriggers - got AF_TRIGGER_START, " +
                            "new AF run is " + currentAfRun);
                }

                mCamera.autoFocus(new Camera.AutoFocusCallback() {
                    @Override
                    public void onAutoFocus(boolean success, Camera camera) {
                        synchronized (mLock) {
                            int latestAfRun = mAfRun;

                            if (VERBOSE) {
                                Log.v(TAG, "onAutoFocus - success " + success + " latest AF run " +
                                        latestAfRun + ", last AF run " + currentAfRun);
                            }

                            // Ignore old auto-focus results, since another trigger was requested
                            if (latestAfRun != currentAfRun) {
                                Log.d(TAG, String.format("onAutoFocus - ignoring AF callback " +
                                        "(old run %d, new run %d)", currentAfRun, latestAfRun));

                                return;
                            }

                            int newAfState = success ?
                                    CONTROL_AF_STATE_FOCUSED_LOCKED :
                                    CONTROL_AF_STATE_NOT_FOCUSED_LOCKED;

                            switch (afMode) {
                                case Parameters.FOCUS_MODE_AUTO:
                                case Parameters.FOCUS_MODE_CONTINUOUS_PICTURE:
                                case Parameters.FOCUS_MODE_CONTINUOUS_VIDEO:
                                case Parameters.FOCUS_MODE_MACRO:
                                    break;
                                // This callback should never be sent in any other AF mode
                                default:
                                    Log.w(TAG, "onAutoFocus - got unexpected onAutoFocus in mode "
                                            + afMode);

                            }

                            mAfState = newAfState;
                        }
                    }
                });

                break;
            case CONTROL_AF_TRIGGER_CANCEL:
                synchronized (mLock) {
                    int updatedAfRun = ++mAfRun;
                    mCamera.cancelAutoFocus();

                    int newAfState = CONTROL_AF_STATE_INACTIVE;
                    mAfState = newAfState;

                    if (VERBOSE) {
                        Log.v(TAG, "processRequestTriggers - got AF_TRIGGER_CANCEL, " +
                                "new AF run is " + updatedAfRun);
                    }
                }

                break;
            case CONTROL_AF_TRIGGER_IDLE:
                // No action necessary. The callbacks will handle transitions.
                break;
            default:
                Log.w(TAG, "mapTriggers - ignoring unknown control.afTrigger = " + afTrigger);
        }
    }

    /**
     * Update the {@code result} camera metadata map with the new value for the
     * {@code control.afState}.
     *
     * <p>AF callbacks are processed in the background, and each call to {@link #mapResultTriggers}
     * will have the latest AF state as reflected by the camera1 callbacks.</p>
     *
     * @param result a non-{@code null} result
     */
    public void mapResultTriggers(CameraMetadataNative result) {
        checkNotNull(result, "result must not be null");

        int newAfState;
        synchronized (mLock) {
            newAfState = mAfState;
        }

        if (VERBOSE && newAfState != mAfStatePrevious) {
            Log.v(TAG, String.format("mapResultTriggers - afState changed from %s to %s",
                    afStateToString(mAfStatePrevious), afStateToString(newAfState)));
        }

        result.set(CaptureResult.CONTROL_AF_STATE, newAfState);

        mAfStatePrevious = newAfState;
    }

    private static String afStateToString(int afState) {
        switch (afState) {
            case CONTROL_AF_STATE_ACTIVE_SCAN:
                return "ACTIVE_SCAN";
            case CONTROL_AF_STATE_FOCUSED_LOCKED:
                return "FOCUSED_LOCKED";
            case CONTROL_AF_STATE_INACTIVE:
                return "INACTIVE";
            case CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
                return "NOT_FOCUSED_LOCKED";
            case CONTROL_AF_STATE_PASSIVE_FOCUSED:
                return "PASSIVE_FOCUSED";
            case CONTROL_AF_STATE_PASSIVE_SCAN:
                return "PASSIVE_SCAN";
            case CONTROL_AF_STATE_PASSIVE_UNFOCUSED:
                return "PASSIVE_UNFOCUSED";
            default :
                return "UNKNOWN(" + afState + ")";
        }
    }
}
+106 −2
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import static android.hardware.camera2.legacy.ParameterUtils.*;
 * Provide legacy-specific implementations of camera2 metadata for legacy devices, such as the
 * camera characteristics.
 */
@SuppressWarnings("deprecation")
public class LegacyMetadataMapper {
    private static final String TAG = "LegacyMetadataMapper";
    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -87,8 +88,8 @@ public class LegacyMetadataMapper {
     */
    static final boolean LIE_ABOUT_AE_STATE = false;
    static final boolean LIE_ABOUT_AE_MAX_REGIONS = false;
    static final boolean LIE_ABOUT_AF = true;
    static final boolean LIE_ABOUT_AF_MAX_REGIONS = true;
    static final boolean LIE_ABOUT_AF = false;
    static final boolean LIE_ABOUT_AF_MAX_REGIONS = false;
    static final boolean LIE_ABOUT_AWB_STATE = false;
    static final boolean LIE_ABOUT_AWB = true;

@@ -161,6 +162,10 @@ public class LegacyMetadataMapper {
         * control.ae*
         */
        mapControlAe(m, p);
        /*
         * control.af*
         */
        mapControlAf(m, p);
        /*
         * control.awb*
         */
@@ -379,6 +384,54 @@ public class LegacyMetadataMapper {
        }
    }


    @SuppressWarnings({"unchecked"})
    private static void mapControlAf(CameraMetadataNative m, Camera.Parameters p) {
        /*
         * control.afAvailableModes
         */
        {
            List<String> focusModes = p.getSupportedFocusModes();

            String[] focusModeStrings = new String[] {
                    Camera.Parameters.FOCUS_MODE_AUTO,
                    Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE,
                    Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO,
                    Camera.Parameters.FOCUS_MODE_EDOF,
                    Camera.Parameters.FOCUS_MODE_INFINITY,
                    Camera.Parameters.FOCUS_MODE_MACRO,
                    Camera.Parameters.FOCUS_MODE_FIXED,
            };

            int[] focusModeInts = new int[] {
                    CONTROL_AF_MODE_AUTO,
                    CONTROL_AF_MODE_CONTINUOUS_PICTURE,
                    CONTROL_AF_MODE_CONTINUOUS_VIDEO,
                    CONTROL_AF_MODE_EDOF,
                    CONTROL_AF_MODE_OFF,
                    CONTROL_AF_MODE_MACRO,
                    CONTROL_AF_MODE_OFF
            };

            List<Integer> afAvail = ArrayUtils.convertStringListToIntList(
                    focusModes, focusModeStrings, focusModeInts);

            // No AF modes supported? That's unpossible!
            if (afAvail == null || afAvail.size() == 0) {
                Log.w(TAG, "No AF modes supported (HAL bug); defaulting to AF_MODE_OFF only");
                afAvail = new ArrayList<Integer>(/*capacity*/1);
                afAvail.add(CONTROL_AF_MODE_OFF);
            }

            m.set(CONTROL_AF_AVAILABLE_MODES, ArrayUtils.toIntArray(afAvail));

            if (VERBOSE) {
                Log.v(TAG, "mapControlAf - control.afAvailableModes set to " +
                        ListUtils.listToString(afAvail));
            }
        }
    }

    private static void mapControlAwb(CameraMetadataNative m, Camera.Parameters p) {
        if (!LIE_ABOUT_AWB) {
            throw new AssertionError("Not implemented yet");
@@ -794,4 +847,55 @@ public class LegacyMetadataMapper {

        return tags;
    }

    /**
     * Convert the requested AF mode into its equivalent supported parameter.
     *
     * @param mode {@code CONTROL_AF_MODE}
     * @param supportedFocusModes list of camera1's supported focus modes
     * @return the stringified af mode, or {@code null} if its not supported
     */
    static String convertAfModeToLegacy(int mode, List<String> supportedFocusModes) {
        if (supportedFocusModes == null || supportedFocusModes.isEmpty()) {
            Log.w(TAG, "No focus modes supported; API1 bug");
            return null;
        }

        String param = null;
        switch (mode) {
            case CONTROL_AF_MODE_AUTO:
                param = Parameters.FOCUS_MODE_AUTO;
                break;
            case CONTROL_AF_MODE_CONTINUOUS_PICTURE:
                param = Parameters.FOCUS_MODE_CONTINUOUS_PICTURE;
                break;
            case CONTROL_AF_MODE_CONTINUOUS_VIDEO:
                param = Parameters.FOCUS_MODE_CONTINUOUS_VIDEO;
                break;
            case CONTROL_AF_MODE_EDOF:
                param = Parameters.FOCUS_MODE_EDOF;
                break;
            case CONTROL_AF_MODE_MACRO:
                param = Parameters.FOCUS_MODE_MACRO;
                break;
            case CONTROL_AF_MODE_OFF:
                if (supportedFocusModes.contains(Parameters.FOCUS_MODE_FIXED)) {
                    param = Parameters.FOCUS_MODE_FIXED;
                } else {
                    param = Parameters.FOCUS_MODE_INFINITY;
                }
        }

        if (!supportedFocusModes.contains(param)) {
            // Weed out bad user input by setting to the first arbitrary focus mode
            String defaultMode = supportedFocusModes.get(0);
            Log.w(TAG,
                    String.format(
                            "convertAfModeToLegacy - ignoring unsupported mode %d, " +
                            "defaulting to %s", mode, defaultMode));
            param = defaultMode;
        }

        return param;
    }
}
+38 −18
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.utils.ListUtils;
import android.hardware.camera2.utils.ParamsUtils;
import android.util.Log;
import android.util.Range;
import android.util.Size;
@@ -38,6 +39,7 @@ import static android.hardware.camera2.CaptureRequest.*;
/**
 * Provide legacy-specific implementations of camera2 CaptureRequest for legacy devices.
 */
@SuppressWarnings("deprecation")
public class LegacyRequestMapper {
    private static final String TAG = "LegacyRequestMapper";
    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -158,7 +160,7 @@ public class LegacyRequestMapper {
        {
            Range<Integer> compensationRange =
                    characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE);
            int compensation = getOrDefault(request,
            int compensation = ParamsUtils.getOrDefault(request,
                    CONTROL_AE_EXPOSURE_COMPENSATION,
                    /*defaultValue*/0);

@@ -192,6 +194,23 @@ public class LegacyRequestMapper {
        // control.aeMode, flash.mode
        mapAeAndFlashMode(request, /*out*/params);

        // control.afMode
        {
            int afMode = ParamsUtils.getOrDefault(request, CONTROL_AF_MODE,
                    /*defaultValue*/CONTROL_AF_MODE_OFF);
            String focusMode = LegacyMetadataMapper.convertAfModeToLegacy(afMode,
                    params.getSupportedFocusModes());

            if (focusMode != null) {
                params.setFocusMode(focusMode);
            }

            if (VERBOSE) {
                Log.v(TAG, "convertRequestToMetadata - control.afMode "
                        + afMode + " mapped to " + focusMode);
            }
        }

        // control.awbLock
        {
            Boolean awbLock = getIfSupported(request, CONTROL_AWB_LOCK, /*defaultValue*/false,
@@ -204,6 +223,21 @@ public class LegacyRequestMapper {

         // TODO: Don't add control.awbLock to availableRequestKeys if it's not supported
        }

        // lens.focusDistance
        {
            boolean infinityFocusSupported =
                    ListUtils.listContains(params.getSupportedFocusModes(),
                            Parameters.FOCUS_MODE_INFINITY);
            Float focusDistance = getIfSupported(request, LENS_FOCUS_DISTANCE,
                    /*defaultValue*/0f, infinityFocusSupported, /*allowedValue*/0f);

            if (focusDistance == null || focusDistance != 0f) {
                Log.w(TAG,
                        "convertRequestToMetadata - Ignoring android.lens.focusDistance "
                                + infinityFocusSupported + ", only 0.0f is supported");
            }
        }
    }

    private static List<Camera.Area> convertMeteringRegionsToLegacy(
@@ -253,8 +287,8 @@ public class LegacyRequestMapper {
    }

    private static void mapAeAndFlashMode(CaptureRequest r, /*out*/Parameters p) {
        int flashMode = getOrDefault(r, FLASH_MODE, FLASH_MODE_OFF);
        int aeMode = getOrDefault(r, CONTROL_AE_MODE, CONTROL_AE_MODE_ON);
        int flashMode = ParamsUtils.getOrDefault(r, FLASH_MODE, FLASH_MODE_OFF);
        int aeMode = ParamsUtils.getOrDefault(r, CONTROL_AE_MODE, CONTROL_AE_MODE_ON);

        List<String> supportedFlashModes = p.getSupportedFlashModes();

@@ -355,20 +389,6 @@ public class LegacyRequestMapper {
        return legacyFps;
    }

    /** Return the value set by the key, or the {@code defaultValue} if no value was set. */
    private static <T> T getOrDefault(CaptureRequest r, CaptureRequest.Key<T> key, T defaultValue) {
        checkNotNull(r, "r must not be null");
        checkNotNull(key, "key must not be null");
        checkNotNull(defaultValue, "defaultValue must not be null");

        T value = r.get(key);
        if (value == null) {
            return defaultValue;
        } else {
            return value;
        }
    }

    /**
     * Return {@code null} if the value is not supported, otherwise return the retrieved key's
     * value from the request (or the default value if it wasn't set).
@@ -382,7 +402,7 @@ public class LegacyRequestMapper {
    private static <T> T getIfSupported(
            CaptureRequest r, CaptureRequest.Key<T> key, T defaultValue, boolean isSupported,
            T allowedValue) {
        T val = getOrDefault(r, key, defaultValue);
        T val = ParamsUtils.getOrDefault(r, key, defaultValue);

        if (!isSupported) {
            if (!Objects.equals(val, allowedValue)) {
+39 −5

File changed.

Preview size limit exceeded, changes collapsed.

Loading