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

Commit 621c88b9 authored by Zhijun He's avatar Zhijun He Committed by Android Git Automerger
Browse files

am 0ccc940a: Merge "Camera2: implement high speed video APIs" into mnc-dev

* commit '0ccc940a':
  Camera2: implement high speed video APIs
parents 961bf18e 0ccc940a
Loading
Loading
Loading
Loading
+45 −1
Original line number Diff line number Diff line
@@ -169,6 +169,9 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
    private final HashSet<Surface> mSurfaceSet;
    private final CameraMetadataNative mSettings;
    private boolean mIsReprocess;
    // If this request is part of constrained high speed request list that was created by
    // {@link CameraDevice#createConstrainedHighSpeedRequestList}.
    private boolean mIsPartOfCHSRequestList = false;
    // Each reprocess request must be tied to a reprocessable session ID.
    // Valid only for reprocess requests (mIsReprocess == true).
    private int mReprocessableSessionId;
@@ -197,6 +200,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
        mSettings = new CameraMetadataNative(source.mSettings);
        mSurfaceSet = (HashSet<Surface>) source.mSurfaceSet.clone();
        mIsReprocess = source.mIsReprocess;
        mIsPartOfCHSRequestList = source.mIsPartOfCHSRequestList;
        mReprocessableSessionId = source.mReprocessableSessionId;
        mUserTag = source.mUserTag;
    }
@@ -320,6 +324,35 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
        return mIsReprocess;
    }

    /**
     * <p>Determine if this request is part of a constrained high speed request list that was
     * created by {@link CameraDevice#createConstrainedHighSpeedRequestList}. A constrained high
     * speed request list contains some constrained high speed capture requests with certain
     * interleaved pattern that is suitable for high speed preview/video streaming. An active
     * constrained high speed capture session only accepts constrained high speed request lists.
     * This method can be used to do the sanity check when a constrained high speed capture session
     * receives a request list via {@link CameraCaptureSession#setRepeatingBurst} or
     * {@link CameraCaptureSession#captureBurst}.
     * </p>
     *
     *
     * @return {@code true} if this request is part of a constrained high speed request list,
     * {@code false} otherwise.
     *
     * @hide
     */
    public boolean isPartOfCRequestList() {
        return mIsPartOfCHSRequestList;
    }

    /**
     * Returns a copy of the underlying {@link CameraMetadataNative}.
     * @hide
     */
    public CameraMetadataNative getNativeCopy() {
        return new CameraMetadataNative(mSettings);
    }

    /**
     * Get the reprocessable session ID this reprocess capture request is associated with.
     *
@@ -546,6 +579,18 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
            mRequest.mUserTag = tag;
        }

        /**
         * <p>Mark this request as part of a constrained high speed request list created by
         * {@link CameraDevice#createConstrainedHighSpeedRequestList}. A constrained high speed
         * request list contains some constrained high speed capture requests with certain
         * interleaved pattern that is suitable for high speed preview/video streaming.</p>
         *
         * @hide
         */
        public void setPartOfCHSRequestList(boolean partOfCHSList) {
            mRequest.mIsPartOfCHSRequestList = partOfCHSList;
        }

        /**
         * Build a request using the current target Surfaces and settings.
         * <p>Note that, although it is possible to create a {@code CaptureRequest} with no target
@@ -563,7 +608,6 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
            return new CaptureRequest(mRequest);
        }


        /**
         * @hide
         */
+1 −1
Original line number Diff line number Diff line
@@ -61,7 +61,7 @@ interface ICameraDeviceUser
     * must be called before any requests can be submitted.
     * <p>
     */
    int endConfigure();
    int endConfigure(boolean isConstrainedHighSpeed);

    int deleteStream(int streamId);

+41 −3
Original line number Diff line number Diff line
@@ -60,6 +60,7 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession {
    private final android.hardware.camera2.impl.CameraDeviceImpl mDeviceImpl;
    /** Internal handler; used for all incoming events to preserve total order */
    private final Handler mDeviceHandler;
    private final boolean mIsConstrainedHighSpeedSession;

    /** Drain Sequence IDs which have been queued but not yet finished with aborted/completed */
    private final TaskDrainer<Integer> mSequenceDrainer;
@@ -88,13 +89,14 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession {
    CameraCaptureSessionImpl(int id, Surface input, List<Surface> outputs,
            CameraCaptureSession.StateCallback callback, Handler stateHandler,
            android.hardware.camera2.impl.CameraDeviceImpl deviceImpl,
            Handler deviceStateHandler, boolean configureSuccess) {
            Handler deviceStateHandler, boolean configureSuccess, boolean isConstrainedHighSpeed) {
        if (outputs == null || outputs.isEmpty()) {
            throw new IllegalArgumentException("outputs must be a non-null, non-empty list");
        } else if (callback == null) {
            throw new IllegalArgumentException("callback must not be null");
        }

        mIsConstrainedHighSpeedSession = isConstrainedHighSpeed;
        mId = id;
        mIdString = String.format("Session %d: ", mId);

@@ -134,6 +136,30 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession {
        }
    }


    private boolean isConstrainedHighSpeedRequestList(List<CaptureRequest> requestList) {
        checkCollectionNotEmpty(requestList, "High speed request list");
        for (CaptureRequest request : requestList) {
            if (!request.isPartOfCRequestList()) {
                return false;
            }
        }
        return true;
    }

    /**
     * If the session is constrained high speed session, it only accept constrained high speed
     * request list.
     */
    private void checkConstrainedHighSpeedRequestSanity(List<CaptureRequest> requestList) {
        if (mIsConstrainedHighSpeedSession) {
            if (!isConstrainedHighSpeedRequestList(requestList)) {
                throw new IllegalArgumentException("It is only allowed to submit a constrained "
                        + "high speed request list to a constrianed high speed session!!!");
            }
        }
    }

    @Override
    public CameraDevice getDevice() {
        return mDeviceImpl;
@@ -155,6 +181,10 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession {
        } else if (request.isReprocess() && request.getReprocessableSessionId() != mId) {
            throw new IllegalArgumentException("capture request was created for another session");
        }
        if (mIsConstrainedHighSpeedSession) {
            throw new UnsupportedOperationException("Constrained high speed session doesn't support"
                    + " this method");
        }

        checkNotClosed();

@@ -178,6 +208,8 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession {
            throw new IllegalArgumentException("Requests must have at least one element");
        }

        checkConstrainedHighSpeedRequestSanity(requests);

        for (CaptureRequest request : requests) {
            if (request.isReprocess()) {
                if (!isReprocessable()) {
@@ -212,7 +244,10 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession {
        } else if (request.isReprocess()) {
            throw new IllegalArgumentException("repeating reprocess requests are not supported");
        }

        if (mIsConstrainedHighSpeedSession) {
            throw new UnsupportedOperationException("Constrained high speed session doesn't support"
                    + " this method");
        }

        checkNotClosed();

@@ -236,6 +271,8 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession {
            throw new IllegalArgumentException("requests must have at least one element");
        }

        checkConstrainedHighSpeedRequestSanity(requests);

        for (CaptureRequest r : requests) {
            if (r.isReprocess()) {
                throw new IllegalArgumentException("repeating reprocess burst requests are not " +
@@ -704,7 +741,8 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession {
                    // everything is idle.
                    try {
                        // begin transition to unconfigured
                        mDeviceImpl.configureStreamsChecked(null, null);
                        mDeviceImpl.configureStreamsChecked(/*inputConfig*/null, /*outputs*/null,
                                /*isConstrainedHighSpeed*/false);
                    } catch (CameraAccessException e) {
                        // OK: do not throw checked exceptions.
                        Log.e(TAG, mIdString + "Exception while unconfiguring outputs: ", e);
+170 −13
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.hardware.camera2.impl;

import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE;

import android.graphics.ImageFormat;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
@@ -35,17 +36,22 @@ import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
import android.hardware.camera2.utils.LongParcelable;
import android.hardware.camera2.utils.SurfaceUtils;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import android.util.Range;
import android.util.Size;
import android.util.SparseArray;
import android.view.Surface;

import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -326,7 +332,8 @@ public class CameraDeviceImpl extends CameraDevice {
        for (Surface s : outputs) {
            outputConfigs.add(new OutputConfiguration(s));
        }
        configureStreamsChecked(/*inputConfig*/null, outputConfigs);
        configureStreamsChecked(/*inputConfig*/null, outputConfigs,
                /*isConstrainedHighSpeed*/false);

    }

@@ -344,12 +351,14 @@ public class CameraDeviceImpl extends CameraDevice {
     *
     * @param inputConfig input configuration or {@code null} for no input
     * @param outputs a list of one or more surfaces, or {@code null} to unconfigure
     * @param isConstrainedHighSpeed If the streams configuration is for constrained high speed output.
     * @return whether or not the configuration was successful
     *
     * @throws CameraAccessException if there were any unexpected problems during configuration
     */
    public boolean configureStreamsChecked(InputConfiguration inputConfig,
            List<OutputConfiguration> outputs) throws CameraAccessException {
            List<OutputConfiguration> outputs, boolean isConstrainedHighSpeed)
                    throws CameraAccessException {
        // Treat a null input the same an empty list
        if (outputs == null) {
            outputs = new ArrayList<OutputConfiguration>();
@@ -422,7 +431,7 @@ public class CameraDeviceImpl extends CameraDevice {
                }

                try {
                    mRemoteDevice.endConfigure();
                    mRemoteDevice.endConfigure(isConstrainedHighSpeed);
                }
                catch (IllegalArgumentException e) {
                    // OK. camera service can reject stream config if it's not supported by HAL
@@ -463,7 +472,8 @@ public class CameraDeviceImpl extends CameraDevice {
        for (Surface surface : outputs) {
            outConfigurations.add(new OutputConfiguration(surface));
        }
        createCaptureSessionInternal(null, outConfigurations, callback, handler);
        createCaptureSessionInternal(null, outConfigurations, callback, handler,
                /*isConstrainedHighSpeed*/false);
    }

    @Override
@@ -475,7 +485,8 @@ public class CameraDeviceImpl extends CameraDevice {
            Log.d(TAG, "createCaptureSessionByOutputConfiguration");
        }

        createCaptureSessionInternal(null, outputConfigurations, callback, handler);
        createCaptureSessionInternal(null, outputConfigurations, callback, handler,
                /*isConstrainedHighSpeed*/false);
    }

    @Override
@@ -494,13 +505,14 @@ public class CameraDeviceImpl extends CameraDevice {
        for (Surface surface : outputs) {
            outConfigurations.add(new OutputConfiguration(surface));
        }
        createCaptureSessionInternal(inputConfig, outConfigurations, callback, handler);
        createCaptureSessionInternal(inputConfig, outConfigurations, callback, handler,
                /*isConstrainedHighSpeed*/false);
    }

    private void createCaptureSessionInternal(InputConfiguration inputConfig,
            List<OutputConfiguration> outputConfigurations,
            CameraCaptureSession.StateCallback callback, Handler handler)
            throws CameraAccessException {
            CameraCaptureSession.StateCallback callback, Handler handler,
            boolean isConstrainedHighSpeed) throws CameraAccessException {
        synchronized(mInterfaceLock) {
            if (DEBUG) {
                Log.d(TAG, "createCaptureSessionInternal");
@@ -508,6 +520,11 @@ public class CameraDeviceImpl extends CameraDevice {

            checkIfCameraClosedOrInError();

            if (isConstrainedHighSpeed && inputConfig != null) {
                throw new IllegalArgumentException("Constrained high speed session doesn't support"
                        + " input configuration yet.");
            }

            // Notify current session that it's going away, before starting camera operations
            // After this call completes, the session is not allowed to call into CameraDeviceImpl
            if (mCurrentSession != null) {
@@ -520,7 +537,8 @@ public class CameraDeviceImpl extends CameraDevice {
            Surface input = null;
            try {
                 // configure streams and then block until IDLE
                configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations);
                configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations,
                        isConstrainedHighSpeed);
                if (inputConfig != null) {
                    input = new Surface();
                    mRemoteDevice.getInputSurface(/*out*/input);
@@ -545,7 +563,7 @@ public class CameraDeviceImpl extends CameraDevice {
            CameraCaptureSessionImpl newSession =
                    new CameraCaptureSessionImpl(mNextSessionId++, input,
                            outSurfaces, callback, handler, this, mDeviceHandler,
                            configureSuccess);
                            configureSuccess, isConstrainedHighSpeed);

            // TODO: wait until current session closes, then create the new session
            mCurrentSession = newSession;
@@ -1906,17 +1924,156 @@ public class CameraDeviceImpl extends CameraDevice {
        return mCharacteristics;
    }

    private void checkConstrainedHighSpeedSurfaces(Collection<Surface> surfaces,
            Range<Integer> fpsRange) {
        if (surfaces == null || surfaces.size() == 0 || surfaces.size() > 2) {
            throw new IllegalArgumentException("Output target surface list must not be null and"
                    + " the size must be 1 or 2");
        }

        StreamConfigurationMap config =
                getCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        List<Size> highSpeedSizes = null;
        if (fpsRange == null) {
            highSpeedSizes = Arrays.asList(config.getHighSpeedVideoSizes());
        } else {
            // Check the FPS range first if provided
            Range<Integer>[] highSpeedFpsRanges = config.getHighSpeedVideoFpsRanges();
            if(!Arrays.asList(highSpeedFpsRanges).contains(fpsRange)) {
                throw new IllegalArgumentException("Fps range " + fpsRange.toString() + " in the"
                        + " request is not a supported high speed fps range " +
                        Arrays.toString(highSpeedFpsRanges));
            }
            highSpeedSizes = Arrays.asList(config.getHighSpeedVideoSizesFor(fpsRange));
        }

        for (Surface surface : surfaces) {
            // Surface size must be supported high speed sizes.
            Size surfaceSize = SurfaceUtils.getSurfaceSize(surface);
            int surfaceFormat = SurfaceUtils.getSurfaceFormat(surface);

            if (surfaceFormat != ImageFormat.PRIVATE) {
                throw new IllegalArgumentException("Surface format is not for preview or"
                        + " hardware video encoding" + surfaceFormat);
            }

            if (!highSpeedSizes.contains(surfaceSize)) {
                throw new IllegalArgumentException("Surface size " + surfaceSize.toString() + " is"
                        + " not part of the high speed supported size list " +
                        Arrays.toString(highSpeedSizes.toArray()));
            }
            // Each output surface must be either preview surface or recording surface.
            if (!SurfaceUtils.isSurfaceForPreview(surface) &&
                    !SurfaceUtils.isSurfaceForHwVideoEncoder(surface)) {
                throw new IllegalArgumentException("This output surface is neither preview nor "
                        + "hardware video encoding surface");
            }
            if (SurfaceUtils.isSurfaceForPreview(surface) &&
                    SurfaceUtils.isSurfaceForHwVideoEncoder(surface)) {
                throw new IllegalArgumentException("This output surface can not be both preview"
                        + " and hardware video encoding surface");
            }
        }

        // For 2 output surface case, they shouldn't be same type.
        if (surfaces.size() == 2) {
            // Up to here, each surface can only be either preview or recording.
            Iterator<Surface> iterator = surfaces.iterator();
            boolean isFirstSurfacePreview =
                    SurfaceUtils.isSurfaceForPreview(iterator.next());
            boolean isSecondSurfacePreview =
                    SurfaceUtils.isSurfaceForPreview(iterator.next());
            if (isFirstSurfacePreview == isSecondSurfacePreview) {
                throw new IllegalArgumentException("The 2 output surfaces must have different"
                        + " type");
            }
        }
    }

    @Override
    public void createConstrainedHighSpeedCaptureSession(List<Surface> outputs,
            android.hardware.camera2.CameraCaptureSession.StateCallback callback, Handler handler)
            throws CameraAccessException {
        // TODO: to be implemented
        throw new UnsupportedOperationException("To be implemented!!!!");
        if (outputs == null || outputs.size() == 0 || outputs.size() > 2) {
            throw new IllegalArgumentException(
                    "Output surface list must not be null and the size must be no more than 2");
        }
        checkConstrainedHighSpeedSurfaces(outputs, /*fpsRange*/null);

        List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size());
        for (Surface surface : outputs) {
            outConfigurations.add(new OutputConfiguration(surface));
        }
        createCaptureSessionInternal(null, outConfigurations, callback, handler,
                /*isConstrainedHighSpeed*/true);
    }

    @Override
    public List<CaptureRequest> createConstrainedHighSpeedRequestList(CaptureRequest request)
            throws CameraAccessException {
        throw new UnsupportedOperationException("To be implemented!!!!");
        if (request == null) {
            throw new IllegalArgumentException("Input capture request must not be null");
        }
        Collection<Surface> outputSurfaces = request.getTargets();
        Range<Integer> fpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
        checkConstrainedHighSpeedSurfaces(outputSurfaces, fpsRange);

        // Request list size: to limit the preview to 30fps, need use maxFps/30; to maximize
        // the preview frame rate, should use maxBatch size for that high speed stream
        // configuration. We choose the former for now.
        int requestListSize = fpsRange.getUpper() / 30;
        List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();

        // Prepare the Request builders: need carry over the request controls.
        // First, create a request builder that will only include preview or recording target.
        CameraMetadataNative requestMetadata = new CameraMetadataNative(request.getNativeCopy());
        CaptureRequest.Builder singleTargetRequestBuilder = new CaptureRequest.Builder(
                requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE);

        // Overwrite the capture intent to make sure a good value is set.
        Surface[] surfaces = (Surface[])outputSurfaces.toArray();
        if (outputSurfaces.size() == 1 && SurfaceUtils.isSurfaceForHwVideoEncoder(surfaces[0])) {
            singleTargetRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT,
                    CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW);
        } else {
            // Video only, or preview + video
            singleTargetRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT,
                    CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD);
        }
        singleTargetRequestBuilder.setPartOfCHSRequestList(/*partOfCHSList*/true);

        // Second, Create a request builder that will include both preview and recording targets.
        CaptureRequest.Builder doubleTargetRequestBuilder = null;
        if (outputSurfaces.size() == 2) {
            doubleTargetRequestBuilder = new CaptureRequest.Builder(
                    requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE);
            doubleTargetRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT,
                    CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD);
            doubleTargetRequestBuilder.addTarget(surfaces[0]);
            doubleTargetRequestBuilder.addTarget(surfaces[1]);
            doubleTargetRequestBuilder.setPartOfCHSRequestList(/*partOfCHSList*/true);
            // Make sure singleTargetRequestBuilder contains only recording surface for
            // preview + recording case.
            Surface recordingSurface = surfaces[0];
            if (!SurfaceUtils.isSurfaceForHwVideoEncoder(recordingSurface)) {
                recordingSurface = surfaces[1];
            }
            singleTargetRequestBuilder.addTarget(recordingSurface);
        } else {
            // Single output case: either recording or preview.
            singleTargetRequestBuilder.addTarget(surfaces[0]);
        }

        // Generate the final request list.
        for (int i = 0; i < requestListSize; i++) {
            if (i == 0 && doubleTargetRequestBuilder != null) {
                // First request should be recording + preview request
                requestList.add(doubleTargetRequestBuilder.build());
            } else {
                requestList.add(singleTargetRequestBuilder.build());
            }
        }

        return Collections.unmodifiableList(requestList);
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -465,7 +465,7 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
    }

    @Override
    public int endConfigure() {
    public int endConfigure(boolean isConstrainedHighSpeed) {
        if (DEBUG) {
            Log.d(TAG, "endConfigure called.");
        }
Loading