diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index 80b5078d9fa04..8e3de616f07b9 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -21,9 +21,11 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.graphics.ImageFormat; +import android.hardware.camera2.extension.IAdvancedExtenderImpl; import android.hardware.camera2.extension.ICameraExtensionsProxyService; import android.hardware.camera2.extension.IImageCaptureExtenderImpl; import android.hardware.camera2.extension.IPreviewExtenderImpl; +import android.hardware.camera2.extension.LatencyRange; import android.hardware.camera2.extension.SizeList; import android.hardware.camera2.params.ExtensionSessionConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; @@ -220,6 +222,7 @@ public final class CameraExtensionCharacteristics { private InitializerFuture mInitFuture = null; private ServiceConnection mConnection = null; private ICameraExtensionsProxyService mProxy = null; + private boolean mSupportsAdvancedExtensions = false; // Singleton, don't allow construction private CameraExtensionManagerGlobal() {} @@ -245,6 +248,11 @@ public final class CameraExtensionCharacteristics { public void onServiceConnected(ComponentName component, IBinder binder) { mProxy = ICameraExtensionsProxyService.Stub.asInterface(binder); mInitFuture.setStatus(true); + try { + mSupportsAdvancedExtensions = mProxy.advancedExtensionsSupported(); + } catch (RemoteException e) { + Log.e(TAG, "Remote IPC failed!"); + } } }; ctx.bindService(intent, mConnection, Context.BIND_AUTO_CREATE | @@ -334,6 +342,10 @@ public final class CameraExtensionCharacteristics { } } + public boolean areAdvancedExtensionsSupported() { + return mSupportsAdvancedExtensions; + } + public IPreviewExtenderImpl initializePreviewExtension(int extensionType) throws RemoteException { synchronized (mLock) { @@ -355,6 +367,17 @@ public final class CameraExtensionCharacteristics { } } } + + public IAdvancedExtenderImpl initializeAdvancedExtension(int extensionType) + throws RemoteException { + synchronized (mLock) { + if (mProxy != null) { + return mProxy.initializeAdvancedExtension(extensionType); + } else { + return null; + } + } + } } /** @@ -371,26 +394,63 @@ public final class CameraExtensionCharacteristics { CameraExtensionManagerGlobal.get().unregisterClient(clientId); } + /** + * @hide + */ + public static boolean areAdvancedExtensionsSupported() { + return CameraExtensionManagerGlobal.get().areAdvancedExtensionsSupported(); + } + /** * @hide */ public static boolean isExtensionSupported(String cameraId, int extensionType, CameraCharacteristics chars) { - Pair extenders; + if (areAdvancedExtensionsSupported()) { + try { + IAdvancedExtenderImpl extender = initializeAdvancedExtension(extensionType); + return extender.isExtensionAvailable(cameraId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to query extension availability! Extension service does not" + + " respond!"); + return false; + } + } else { + Pair extenders; + try { + extenders = initializeExtension(extensionType); + } catch (IllegalArgumentException e) { + return false; + } + + try { + return extenders.first.isExtensionAvailable(cameraId, chars.getNativeMetadata()) && + extenders.second.isExtensionAvailable(cameraId, chars.getNativeMetadata()); + } catch (RemoteException e) { + Log.e(TAG, "Failed to query extension availability! Extension service does not" + + " respond!"); + return false; + } + } + } + + /** + * @hide + */ + public static IAdvancedExtenderImpl initializeAdvancedExtension(@Extension int extensionType) { + IAdvancedExtenderImpl extender; try { - extenders = initializeExtension(extensionType); - } catch (IllegalArgumentException e) { - return false; + extender = CameraExtensionManagerGlobal.get().initializeAdvancedExtension( + extensionType); + } catch (RemoteException e) { + throw new IllegalStateException("Failed to initialize extension: " + extensionType); } - try { - return extenders.first.isExtensionAvailable(cameraId, chars.getNativeMetadata()) && - extenders.second.isExtensionAvailable(cameraId, chars.getNativeMetadata()); - } catch (RemoteException e) { - Log.e(TAG, "Failed to query extension availability! Extension service does not" - + " respond!"); - return false; + if (extender == null) { + throw new IllegalArgumentException("Unknown extension: " + extensionType); } + + return extender; } /** @@ -487,13 +547,21 @@ public final class CameraExtensionCharacteristics { throw new IllegalArgumentException("Unsupported extension"); } - Pair extenders = - initializeExtension(extension); StreamConfigurationMap streamMap = mChars.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); - extenders.first.init(mCameraId, mChars.getNativeMetadata()); - return generateSupportedSizes(extenders.first.getSupportedResolutions(), - ImageFormat.PRIVATE, streamMap); + if (areAdvancedExtensionsSupported()) { + IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); + extender.init(mCameraId); + return generateSupportedSizes( + extender.getSupportedPreviewOutputResolutions(mCameraId), + ImageFormat.PRIVATE, streamMap); + } else { + Pair extenders = + initializeExtension(extension); + extenders.first.init(mCameraId, mChars.getNativeMetadata()); + return generateSupportedSizes(extenders.first.getSupportedResolutions(), + ImageFormat.PRIVATE, streamMap); + } } catch (RemoteException e) { Log.e(TAG, "Failed to query the extension supported sizes! Extension service does" + " not respond!"); @@ -536,31 +604,47 @@ public final class CameraExtensionCharacteristics { throw new IllegalArgumentException("Unsupported extension"); } - Pair extenders = - initializeExtension(extension); StreamConfigurationMap streamMap = mChars.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); - if (format == ImageFormat.YUV_420_888) { - extenders.second.init(mCameraId, mChars.getNativeMetadata()); - if (extenders.second.getCaptureProcessor() == null) { - // Extensions that don't implement any capture processor are limited to - // JPEG only! - return new ArrayList<>(); - } - return generateSupportedSizes(extenders.second.getSupportedResolutions(), - format, streamMap); - } else if (format == ImageFormat.JPEG) { - extenders.second.init(mCameraId, mChars.getNativeMetadata()); - if (extenders.second.getCaptureProcessor() != null) { - // The framework will perform the additional encoding pass on the - // processed YUV_420 buffers. - return generateJpegSupportedSizes( - extenders.second.getSupportedResolutions(), streamMap); - } else { - return generateSupportedSizes(null, format, streamMap); + if (areAdvancedExtensionsSupported()) { + switch(format) { + case ImageFormat.YUV_420_888: + case ImageFormat.JPEG: + break; + default: + throw new IllegalArgumentException("Unsupported format: " + format); } + IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); + extender.init(mCameraId); + return generateSupportedSizes(extender.getSupportedCaptureOutputResolutions( + mCameraId), format, streamMap); } else { - throw new IllegalArgumentException("Unsupported format: " + format); + if (format == ImageFormat.YUV_420_888) { + Pair extenders = + initializeExtension(extension); + extenders.second.init(mCameraId, mChars.getNativeMetadata()); + if (extenders.second.getCaptureProcessor() == null) { + // Extensions that don't implement any capture processor are limited to + // JPEG only! + return new ArrayList<>(); + } + return generateSupportedSizes(extenders.second.getSupportedResolutions(), + format, streamMap); + } else if (format == ImageFormat.JPEG) { + Pair extenders = + initializeExtension(extension); + extenders.second.init(mCameraId, mChars.getNativeMetadata()); + if (extenders.second.getCaptureProcessor() != null) { + // The framework will perform the additional encoding pass on the + // processed YUV_420 buffers. + return generateJpegSupportedSizes( + extenders.second.getSupportedResolutions(), streamMap); + } else { + return generateSupportedSizes(null, format, streamMap); + } + } else { + throw new IllegalArgumentException("Unsupported format: " + format); + } } } finally { unregisterClient(clientId); @@ -608,6 +692,23 @@ public final class CameraExtensionCharacteristics { if (!isExtensionSupported(mCameraId, extension, mChars)) { throw new IllegalArgumentException("Unsupported extension"); } + + if (areAdvancedExtensionsSupported()) { + IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); + extender.init(mCameraId); + android.hardware.camera2.extension.Size sz = + new android.hardware.camera2.extension.Size(); + sz.width = captureOutputSize.getWidth(); + sz.height = captureOutputSize.getHeight(); + LatencyRange latencyRange = extender.getEstimatedCaptureLatencyRange(mCameraId, + sz, format); + if (latencyRange != null) { + return new Range(latencyRange.min, latencyRange.max); + } + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to query the extension capture latency! Extension service does" + + " not respond!"); } finally { unregisterClient(clientId); } diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 6ff68c11a9748..d32341fddff95 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -229,6 +229,33 @@ public class CaptureResult extends CameraMetadata> { mFrameNumber = extras.getFrameNumber(); } + /** + * Takes ownership of the passed-in properties object + * + *

For internal use only

+ * @hide + */ + public CaptureResult(String cameraId, CameraMetadataNative results, CaptureRequest parent, + int requestId, long frameNumber) { + if (results == null) { + throw new IllegalArgumentException("results was null"); + } + + if (parent == null) { + throw new IllegalArgumentException("parent was null"); + } + + mResults = CameraMetadataNative.move(results); + if (mResults.isEmpty()) { + throw new AssertionError("Results must not be empty"); + } + setNativeInstance(mResults); + mCameraId = cameraId; + mRequest = parent; + mSequenceId = requestId; + mFrameNumber = frameNumber; + } + /** * Returns a copy of the underlying {@link CameraMetadataNative}. * @hide diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java index df8eeccbe8007..ac7f2ca6a427c 100644 --- a/core/java/android/hardware/camera2/TotalCaptureResult.java +++ b/core/java/android/hardware/camera2/TotalCaptureResult.java @@ -93,6 +93,36 @@ public final class TotalCaptureResult extends CaptureResult { } } + /** + * Takes ownership of the passed-in camera metadata and the partial results + * + * @param partials a list of partial results; {@code null} will be substituted for an empty list + * @hide + */ + public TotalCaptureResult(String logicalCameraId, CameraMetadataNative results, + CaptureRequest parent, int requestId, long frameNumber, List partials, + int sessionId, PhysicalCaptureResultInfo[] physicalResults) { + super(logicalCameraId, results, parent, requestId, frameNumber); + + if (partials == null) { + mPartialResults = new ArrayList<>(); + } else { + mPartialResults = partials; + } + + mSessionId = sessionId; + + mPhysicalCaptureResults = new HashMap(); + for (PhysicalCaptureResultInfo onePhysicalResult : physicalResults) { + TotalCaptureResult physicalResult = new TotalCaptureResult( + onePhysicalResult.getCameraId(), onePhysicalResult.getCameraMetadata(), + parent, requestId, frameNumber, /*partials*/null, sessionId, + new PhysicalCaptureResultInfo[0]); + mPhysicalCaptureResults.put(onePhysicalResult.getCameraId(), + physicalResult); + } + } + /** * Creates a request-less result. * diff --git a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl new file mode 100644 index 0000000000000..a61bb3304c120 --- /dev/null +++ b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2021, 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.extension; + +import android.hardware.camera2.extension.Size; +import android.hardware.camera2.extension.OutputConfigId; +import android.view.Surface; + +/** @hide */ +parcelable CameraOutputConfig +{ + Size size; + Surface surface; + int imageFormat; + int capacity; + + const int TYPE_SURFACE = 0; + const int TYPE_IMAGEREADER = 1; + const int TYPE_MULTIRES_IMAGEREADER = 2; + int type; + + OutputConfigId outputId; + int surfaceGroupId; + String physicalCameraId; + List surfaceSharingOutputConfigs; +} diff --git a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl new file mode 100644 index 0000000000000..97ce183ac1916 --- /dev/null +++ b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2021, 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.extension; + +import android.hardware.camera2.extension.CameraOutputConfig; +import android.hardware.camera2.impl.CameraMetadataNative; + +/** @hide */ +parcelable CameraSessionConfig +{ + List outputConfigs; + CameraMetadataNative sessionParameter; + int sessionTemplateId; +} diff --git a/core/java/android/hardware/camera2/extension/CaptureFailure.aidl b/core/java/android/hardware/camera2/extension/CaptureFailure.aidl new file mode 100644 index 0000000000000..d48696c9ea852 --- /dev/null +++ b/core/java/android/hardware/camera2/extension/CaptureFailure.aidl @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2021, 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.extension; + +import android.hardware.camera2.CaptureRequest; + +/** @hide */ +parcelable CaptureFailure +{ + CaptureRequest request; + int reason; + boolean dropped; + int sequenceId; + long frameNumber; + String errorPhysicalCameraId; +} diff --git a/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl new file mode 100644 index 0000000000000..f279c59b812c9 --- /dev/null +++ b/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021, 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.extension; + +import android.hardware.camera2.extension.ISessionProcessorImpl; +import android.hardware.camera2.extension.LatencyRange; +import android.hardware.camera2.extension.Size; +import android.hardware.camera2.extension.SizeList; + +/** @hide */ +interface IAdvancedExtenderImpl +{ + boolean isExtensionAvailable(in String cameraId); + void init(in String cameraId); + LatencyRange getEstimatedCaptureLatencyRange(in String cameraId, in Size outputSize, + int format); + @nullable List getSupportedPreviewOutputResolutions(in String cameraId); + @nullable List getSupportedCaptureOutputResolutions(in String cameraId); + ISessionProcessorImpl getSessionProcessor(); +} diff --git a/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl b/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl index 2a6d22cd807cf..bc29e9a481aed 100644 --- a/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl +++ b/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl @@ -15,6 +15,7 @@ */ package android.hardware.camera2.extension; +import android.hardware.camera2.extension.IAdvancedExtenderImpl; import android.hardware.camera2.extension.IPreviewExtenderImpl; import android.hardware.camera2.extension.IImageCaptureExtenderImpl; @@ -23,6 +24,8 @@ interface ICameraExtensionsProxyService { long registerClient(); void unregisterClient(long clientId); + boolean advancedExtensionsSupported(); @nullable IPreviewExtenderImpl initializePreviewExtension(int extensionType); @nullable IImageCaptureExtenderImpl initializeImageExtension(int extensionType); + @nullable IAdvancedExtenderImpl initializeAdvancedExtension(int extensionType); } diff --git a/core/java/android/hardware/camera2/extension/ICaptureCallback.aidl b/core/java/android/hardware/camera2/extension/ICaptureCallback.aidl new file mode 100644 index 0000000000000..6ab0ad2c74177 --- /dev/null +++ b/core/java/android/hardware/camera2/extension/ICaptureCallback.aidl @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2021, 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.extension; + +import android.hardware.camera2.extension.Request; + +/** @hide */ +interface ICaptureCallback +{ + void onCaptureStarted(int captureSequenceId, long timestamp); + void onCaptureProcessStarted(int captureSequenceId); + void onCaptureFailed(int captureSequenceId); + void onCaptureSequenceCompleted(int captureSequenceId); + void onCaptureSequenceAborted(int captureSequenceId); +} diff --git a/core/java/android/hardware/camera2/extension/IImageProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/IImageProcessorImpl.aidl new file mode 100644 index 0000000000000..f365469eaecc0 --- /dev/null +++ b/core/java/android/hardware/camera2/extension/IImageProcessorImpl.aidl @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2021, 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.extension; + +import android.hardware.camera2.extension.OutputConfigId; +import android.hardware.camera2.extension.ParcelImage; + +/** @hide */ +interface IImageProcessorImpl +{ + void onNextImageAvailable(in OutputConfigId outputConfigId, in ParcelImage image); +} diff --git a/core/java/android/hardware/camera2/extension/IRequestCallback.aidl b/core/java/android/hardware/camera2/extension/IRequestCallback.aidl new file mode 100644 index 0000000000000..5f308b780b4fa --- /dev/null +++ b/core/java/android/hardware/camera2/extension/IRequestCallback.aidl @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2021, 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.extension; + +import android.hardware.camera2.extension.CaptureFailure; +import android.hardware.camera2.extension.ParcelCaptureResult; +import android.hardware.camera2.extension.ParcelTotalCaptureResult; + +/** @hide */ +interface IRequestCallback +{ + void onCaptureStarted(int requestId, long frameNumber, long timestamp); + void onCaptureProgressed(int requestId, in ParcelCaptureResult partialResult); + void onCaptureCompleted(int requestId, in ParcelTotalCaptureResult totalCaptureResult); + void onCaptureFailed(int requestId, in CaptureFailure captureFailure); + void onCaptureBufferLost(int requestId, long frameNumber, int outputStreamId); + void onCaptureSequenceCompleted(int sequenceId, long frameNumber); + void onCaptureSequenceAborted(int sequenceId); +} diff --git a/core/java/android/hardware/camera2/extension/IRequestProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/IRequestProcessorImpl.aidl new file mode 100644 index 0000000000000..52595a8c5a2d6 --- /dev/null +++ b/core/java/android/hardware/camera2/extension/IRequestProcessorImpl.aidl @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2021, 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.extension; + +import android.hardware.camera2.extension.IImageProcessorImpl; +import android.hardware.camera2.extension.IRequestCallback; +import android.hardware.camera2.extension.OutputConfigId; +import android.hardware.camera2.extension.Request; + +/** @hide */ +interface IRequestProcessorImpl +{ + void setImageProcessor(in OutputConfigId outputConfigId, in IImageProcessorImpl imageProcessor); + boolean submit(in Request request, in IRequestCallback callback); + boolean submitBurst(in List requests, in IRequestCallback callback); + boolean setRepeating(in Request request, in IRequestCallback callback); + void abortCaptures(); + void stopRepeating(); +} diff --git a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl new file mode 100644 index 0000000000000..6fdf4df775bc0 --- /dev/null +++ b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2021, 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.extension; + +import android.hardware.camera2.extension.CameraSessionConfig; +import android.hardware.camera2.extension.ICaptureCallback; +import android.hardware.camera2.extension.IRequestProcessorImpl; +import android.hardware.camera2.extension.OutputSurface; + +/** @hide */ +interface ISessionProcessorImpl +{ + CameraSessionConfig initSession(in String cameraId, in OutputSurface previewSurface, + in OutputSurface imageCaptureSurface); + void deInitSession(); + void onCaptureSessionStart(IRequestProcessorImpl requestProcessor); + void onCaptureSessionEnd(); + int startRepeating(in ICaptureCallback callback); + void stopRepeating(); + int startCapture(in ICaptureCallback callback, int jpegRotation, int jpegQuality); +} diff --git a/core/java/android/hardware/camera2/extension/LatencyRange.aidl b/core/java/android/hardware/camera2/extension/LatencyRange.aidl new file mode 100644 index 0000000000000..9bedbb0b0c780 --- /dev/null +++ b/core/java/android/hardware/camera2/extension/LatencyRange.aidl @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2021, 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.extension; + +/** @hide */ +parcelable LatencyRange +{ + long min; + long max; +} diff --git a/core/java/android/hardware/camera2/extension/OutputConfigId.aidl b/core/java/android/hardware/camera2/extension/OutputConfigId.aidl new file mode 100644 index 0000000000000..b27f29adc2320 --- /dev/null +++ b/core/java/android/hardware/camera2/extension/OutputConfigId.aidl @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2021, 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.extension; + +/** @hide */ +parcelable OutputConfigId +{ + int id; +} diff --git a/core/java/android/hardware/camera2/extension/OutputSurface.aidl b/core/java/android/hardware/camera2/extension/OutputSurface.aidl new file mode 100644 index 0000000000000..841537918f659 --- /dev/null +++ b/core/java/android/hardware/camera2/extension/OutputSurface.aidl @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2021, 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.extension; + +import android.hardware.camera2.extension.Size; +import android.view.Surface; + +/** @hide */ +parcelable OutputSurface +{ + Surface surface; + Size size; + int imageFormat; +} diff --git a/core/java/android/hardware/camera2/extension/ParcelCaptureResult.aidl b/core/java/android/hardware/camera2/extension/ParcelCaptureResult.aidl new file mode 100644 index 0000000000000..f99b256d81bb6 --- /dev/null +++ b/core/java/android/hardware/camera2/extension/ParcelCaptureResult.aidl @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2021, 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.extension; + +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.impl.CameraMetadataNative; + +/** @hide */ +parcelable ParcelCaptureResult +{ + String cameraId; + CameraMetadataNative results; + CaptureRequest parent; + int sequenceId; + long frameNumber; +} diff --git a/core/java/android/hardware/camera2/extension/ParcelTotalCaptureResult.aidl b/core/java/android/hardware/camera2/extension/ParcelTotalCaptureResult.aidl new file mode 100644 index 0000000000000..8021a57e9ed13 --- /dev/null +++ b/core/java/android/hardware/camera2/extension/ParcelTotalCaptureResult.aidl @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2021, 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.extension; + +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.extension.ParcelCaptureResult; +import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.impl.PhysicalCaptureResultInfo; + +/** @hide */ +parcelable ParcelTotalCaptureResult +{ + String logicalCameraId; + CameraMetadataNative results; + CaptureRequest parent; + int sequenceId; + long frameNumber; + List partials; + int sessionId; + List physicalResult; +} diff --git a/core/java/android/hardware/camera2/extension/Request.aidl b/core/java/android/hardware/camera2/extension/Request.aidl new file mode 100644 index 0000000000000..d9934dca2bdd5 --- /dev/null +++ b/core/java/android/hardware/camera2/extension/Request.aidl @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2021, 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.extension; + +import android.hardware.camera2.extension.OutputConfigId; +import android.hardware.camera2.impl.CameraMetadataNative; + +/** @hide */ +parcelable Request +{ + List targetOutputConfigIds; + CameraMetadataNative parameters; + int templateId; + int requestId; +} diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java new file mode 100644 index 0000000000000..abc487db5bdbe --- /dev/null +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -0,0 +1,917 @@ +/* + * Copyright (C) 2021 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.impl; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.graphics.ImageFormat; +import android.graphics.SurfaceTexture; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CameraExtensionCharacteristics; +import android.hardware.camera2.CameraExtensionSession; +import android.hardware.camera2.CameraManager; +import android.hardware.camera2.CaptureFailure; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.TotalCaptureResult; +import android.hardware.camera2.extension.CameraOutputConfig; +import android.hardware.camera2.extension.CameraSessionConfig; +import android.hardware.camera2.extension.IAdvancedExtenderImpl; +import android.hardware.camera2.extension.ICaptureCallback; +import android.hardware.camera2.extension.IImageProcessorImpl; +import android.hardware.camera2.extension.IRequestCallback; +import android.hardware.camera2.extension.IRequestProcessorImpl; +import android.hardware.camera2.extension.ISessionProcessorImpl; +import android.hardware.camera2.extension.OutputConfigId; +import android.hardware.camera2.extension.OutputSurface; +import android.hardware.camera2.extension.ParcelCaptureResult; +import android.hardware.camera2.extension.ParcelImage; +import android.hardware.camera2.extension.ParcelTotalCaptureResult; +import android.hardware.camera2.extension.Request; +import android.hardware.camera2.params.ExtensionSessionConfiguration; +import android.hardware.camera2.params.OutputConfiguration; +import android.hardware.camera2.params.SessionConfiguration; +import android.hardware.camera2.utils.SurfaceUtils; +import android.media.Image; +import android.media.ImageReader; +import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.util.Log; +import android.util.Size; +import android.view.Surface; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; + +public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSession { + private static final String TAG = "CameraAdvancedExtensionSessionImpl"; + + private final Executor mExecutor; + private final CameraDevice mCameraDevice; + private final long mExtensionClientId; + private final Handler mHandler; + private final HandlerThread mHandlerThread; + private final CameraExtensionSession.StateCallback mCallbacks; + private final IAdvancedExtenderImpl mAdvancedExtender; + // maps camera outputs to extension output ids + private final HashMap mSurfaceIdMap = new HashMap<>(); + // maps camera extension output ids to camera registered image readers + private final HashMap mReaderMap = new HashMap<>(); + private final RequestProcessor mRequestProcessor = new RequestProcessor(); + + private Surface mClientRepeatingRequestSurface; + private Surface mClientCaptureSurface; + private CameraCaptureSession mCaptureSession = null; + private ISessionProcessorImpl mSessionProcessor = null; + + private boolean mInitialized; + + + // Lock to synchronize cross-thread access to device public interface + final Object mInterfaceLock = new Object(); // access from this class and Session only! + + /** + * @hide + */ + @RequiresPermission(android.Manifest.permission.CAMERA) + public static CameraAdvancedExtensionSessionImpl createCameraAdvancedExtensionSession( + @NonNull CameraDevice cameraDevice, @NonNull Context ctx, + @NonNull ExtensionSessionConfiguration config) + throws CameraAccessException, RemoteException { + long clientId = CameraExtensionCharacteristics.registerClient(ctx); + if (clientId < 0) { + throw new UnsupportedOperationException("Unsupported extension!"); + } + + String cameraId = cameraDevice.getId(); + CameraManager manager = ctx.getSystemService(CameraManager.class); + CameraCharacteristics chars = manager.getCameraCharacteristics(cameraId); + CameraExtensionCharacteristics extensionChars = new CameraExtensionCharacteristics(ctx, + cameraId, chars); + + if (!CameraExtensionCharacteristics.isExtensionSupported(cameraDevice.getId(), + config.getExtension(), chars)) { + throw new UnsupportedOperationException("Unsupported extension type: " + + config.getExtension()); + } + + if (config.getOutputConfigurations().isEmpty() || + config.getOutputConfigurations().size() > 2) { + throw new IllegalArgumentException("Unexpected amount of output surfaces, received: " + + config.getOutputConfigurations().size() + " expected <= 2"); + } + + int suitableSurfaceCount = 0; + List supportedPreviewSizes = extensionChars.getExtensionSupportedSizes( + config.getExtension(), SurfaceTexture.class); + Surface repeatingRequestSurface = CameraExtensionUtils.getRepeatingRequestSurface( + config.getOutputConfigurations(), supportedPreviewSizes); + if (repeatingRequestSurface != null) { + suitableSurfaceCount++; + } + + HashMap> supportedCaptureSizes = new HashMap<>(); + for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) { + List supportedSizes = extensionChars.getExtensionSupportedSizes( + config.getExtension(), format); + if (supportedSizes != null) { + supportedCaptureSizes.put(format, supportedSizes); + } + } + Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface( + config.getOutputConfigurations(), supportedCaptureSizes); + if (burstCaptureSurface != null) { + suitableSurfaceCount++; + } + + if (suitableSurfaceCount != config.getOutputConfigurations().size()) { + throw new IllegalArgumentException("One or more unsupported output surfaces found!"); + } + + IAdvancedExtenderImpl extender = CameraExtensionCharacteristics.initializeAdvancedExtension( + config.getExtension()); + extender.init(cameraId); + + CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(clientId, + extender, cameraDevice, repeatingRequestSurface, burstCaptureSurface, + config.getStateCallback(), config.getExecutor()); + ret.initialize(); + + return ret; + } + + private CameraAdvancedExtensionSessionImpl(long extensionClientId, + @NonNull IAdvancedExtenderImpl extender, @NonNull CameraDevice cameraDevice, + @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface, + @NonNull CameraExtensionSession.StateCallback callback, @NonNull Executor executor) { + mExtensionClientId = extensionClientId; + mAdvancedExtender = extender; + mCameraDevice = cameraDevice; + mCallbacks = callback; + mExecutor = executor; + mClientRepeatingRequestSurface = repeatingRequestSurface; + mClientCaptureSurface = burstCaptureSurface; + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + mInitialized = false; + } + + /** + * @hide + */ + public synchronized void initialize() throws CameraAccessException, RemoteException { + if (mInitialized) { + Log.d(TAG, "Session already initialized"); + return; + } + + OutputSurface previewSurface = initializeParcelable(mClientRepeatingRequestSurface); + OutputSurface captureSurface = initializeParcelable(mClientCaptureSurface); + mSessionProcessor = mAdvancedExtender.getSessionProcessor(); + CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mCameraDevice.getId(), + previewSurface, captureSurface); + List outputConfigs = sessionConfig.outputConfigs; + // map camera output ids to output configurations + HashMap cameraOutputs = new HashMap<>(); + for (CameraOutputConfig output : outputConfigs) { + OutputConfiguration cameraOutput = null; + switch(output.type) { + case CameraOutputConfig.TYPE_SURFACE: + if (output.surface == null) { + Log.w(TAG, "Unsupported client output id: " + output.outputId.id + + ", skipping!"); + continue; + } + cameraOutput = new OutputConfiguration(output.surfaceGroupId, + output.surface); + break; + case CameraOutputConfig.TYPE_IMAGEREADER: + if ((output.imageFormat == ImageFormat.UNKNOWN) || (output.size.width <= 0) || + (output.size.height <= 0)) { + Log.w(TAG, "Unsupported client output id: " + output.outputId.id + + ", skipping!"); + continue; + } + ImageReader reader = ImageReader.newInstance(output.size.width, + output.size.height, output.imageFormat, output.capacity); + mReaderMap.put(output.outputId.id, reader); + cameraOutput = new OutputConfiguration(output.surfaceGroupId, + reader.getSurface()); + break; + case CameraOutputConfig.TYPE_MULTIRES_IMAGEREADER: + // TBD + default: + throw new IllegalArgumentException("Unsupported output config type: " + + output.type); + } + cameraOutput.setPhysicalCameraId(output.physicalCameraId); + cameraOutputs.put(output.outputId.id, cameraOutput); + } + + ArrayList outputList = new ArrayList<>(); + for (CameraOutputConfig output : outputConfigs) { + if (!cameraOutputs.containsKey(output.outputId.id)) { + // Shared surface already removed by a previous iteration + continue; + } + OutputConfiguration outConfig = cameraOutputs.get(output.outputId.id); + if ((output.surfaceSharingOutputConfigs != null) && + !output.surfaceSharingOutputConfigs.isEmpty()) { + outConfig.enableSurfaceSharing(); + for (OutputConfigId outputId : output.surfaceSharingOutputConfigs) { + outConfig.addSurface(cameraOutputs.get(outputId.id).getSurface()); + cameraOutputs.remove(outputId.id); + } + } + outputList.add(outConfig); + mSurfaceIdMap.put(outConfig.getSurface(), output.outputId.id); + } + + SessionConfiguration sessionConfiguration = new SessionConfiguration( + SessionConfiguration.SESSION_REGULAR, outputList, + new CameraExtensionUtils.HandlerExecutor(mHandler), new SessionStateHandler()); + + if ((sessionConfig.sessionParameter != null) && + (!sessionConfig.sessionParameter.isEmpty())) { + CaptureRequest.Builder requestBuilder = mCameraDevice.createCaptureRequest( + sessionConfig.sessionTemplateId); + CaptureRequest sessionRequest = requestBuilder.build(); + CameraMetadataNative.update(sessionRequest.getNativeMetadata(), + sessionConfig.sessionParameter); + sessionConfiguration.setSessionParameters(sessionRequest); + } + + mCameraDevice.createCaptureSession(sessionConfiguration); + } + + private static ParcelCaptureResult initializeParcelable(CaptureResult result) { + ParcelCaptureResult ret = new ParcelCaptureResult(); + ret.cameraId = result.getCameraId(); + ret.results = result.getNativeMetadata(); + ret.parent = result.getRequest(); + ret.sequenceId = result.getSequenceId(); + ret.frameNumber = result.getFrameNumber(); + + return ret; + } + + private static ParcelTotalCaptureResult initializeParcelable(TotalCaptureResult totalResult) { + ParcelTotalCaptureResult ret = new ParcelTotalCaptureResult(); + ret.logicalCameraId = totalResult.getCameraId(); + ret.results = totalResult.getNativeMetadata(); + ret.parent = totalResult.getRequest(); + ret.sequenceId = totalResult.getSequenceId(); + ret.frameNumber = totalResult.getFrameNumber(); + ret.sessionId = totalResult.getSessionId(); + ret.partials = new ArrayList<>(totalResult.getPartialResults().size()); + for (CaptureResult partial : totalResult.getPartialResults()) { + ret.partials.add(initializeParcelable(partial)); + } + Map physicalResults = + totalResult.getPhysicalCameraTotalResults(); + ret.physicalResult = new ArrayList<>(physicalResults.size()); + for (TotalCaptureResult physicalResult : physicalResults.values()) { + ret.physicalResult.add(new PhysicalCaptureResultInfo(physicalResult.getCameraId(), + physicalResult.getNativeMetadata())); + } + + return ret; + } + + private static OutputSurface initializeParcelable(Surface s) { + OutputSurface ret = new OutputSurface(); + if (s != null) { + ret.surface = s; + ret.size = new android.hardware.camera2.extension.Size(); + Size surfaceSize = SurfaceUtils.getSurfaceSize(s); + ret.size.width = surfaceSize.getWidth(); + ret.size.height = surfaceSize.getHeight(); + ret.imageFormat = SurfaceUtils.getSurfaceFormat(s); + } else { + ret.surface = null; + ret.size = new android.hardware.camera2.extension.Size(); + ret.size.width = -1; + ret.size.height = -1; + ret.imageFormat = ImageFormat.UNKNOWN; + } + + return ret; + } + + @Override + public @NonNull CameraDevice getDevice() { + synchronized (mInterfaceLock) { + return mCameraDevice; + } + } + + @Override + public int setRepeatingRequest(@NonNull CaptureRequest request, @NonNull Executor executor, + @NonNull ExtensionCaptureCallback listener) throws CameraAccessException { + int seqId = -1; + synchronized (mInterfaceLock) { + if (!mInitialized) { + throw new IllegalStateException("Uninitialized component"); + } + + if (mClientRepeatingRequestSurface == null) { + throw new IllegalArgumentException("No registered preview surface"); + } + + if (!request.containsTarget(mClientRepeatingRequestSurface) || + (request.getTargets().size() != 1)) { + throw new IllegalArgumentException("Invalid repeating request output target!"); + } + + try { + seqId = mSessionProcessor.startRepeating(new RequestCallbackHandler(request, + executor, listener)); + } catch (RemoteException e) { + throw new CameraAccessException(CameraAccessException.CAMERA_ERROR, + "Failed to enable repeating request, extension service failed to respond!"); + } + } + + return seqId; + } + + @Override + public int capture(@NonNull CaptureRequest request, + @NonNull Executor executor, + @NonNull ExtensionCaptureCallback listener) throws CameraAccessException { + int seqId = -1; + synchronized (mInterfaceLock) { + if (!mInitialized) { + throw new IllegalStateException("Uninitialized component"); + } + + if (mClientCaptureSurface == null) { + throw new IllegalArgumentException("No output surface registered for single" + + " requests!"); + } + + if (!request.containsTarget(mClientCaptureSurface) || + (request.getTargets().size() != 1)) { + throw new IllegalArgumentException("Invalid single capture output target!"); + } + + try { + // This will override the extension capture stage jpeg parameters with the user set + // jpeg quality and rotation. This will guarantee that client configured jpeg + // parameters always have highest priority. + Integer jpegRotation = request.get(CaptureRequest.JPEG_ORIENTATION); + if (jpegRotation == null) { + jpegRotation = CameraExtensionUtils.JPEG_DEFAULT_ROTATION; + } + Byte jpegQuality = request.get(CaptureRequest.JPEG_QUALITY); + if (jpegQuality == null) { + jpegQuality = CameraExtensionUtils.JPEG_DEFAULT_QUALITY; + } + + seqId = mSessionProcessor.startCapture(new RequestCallbackHandler(request, + executor, listener), jpegRotation, jpegQuality); + } catch (RemoteException e) { + throw new CameraAccessException(CameraAccessException.CAMERA_ERROR, + "Failed to submit capture request, extension service failed to respond!"); + } + } + + return seqId; + } + + @Override + public void stopRepeating() throws CameraAccessException { + synchronized (mInterfaceLock) { + if (!mInitialized) { + throw new IllegalStateException("Uninitialized component"); + } + + mCaptureSession.stopRepeating(); + + try { + mSessionProcessor.stopRepeating(); + } catch (RemoteException e) { + throw new CameraAccessException(CameraAccessException.CAMERA_ERROR, + "Failed to notify about the end of repeating request, extension service" + + " failed to respond!"); + } + } + } + + @Override + public void close() throws CameraAccessException { + synchronized (mInterfaceLock) { + if (mInitialized) { + try { + mCaptureSession.stopRepeating(); + mSessionProcessor.stopRepeating(); + mSessionProcessor.onCaptureSessionEnd(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to stop the repeating request or end the session," + + " , extension service does not respond!") ; + } + mCaptureSession.close(); + } + } + } + + public void release() { + synchronized (mInterfaceLock) { + mInitialized = false; + mHandlerThread.quitSafely(); + + if (mSessionProcessor != null) { + try { + mSessionProcessor.deInitSession(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to de-initialize session processor, extension service" + + " does not respond!") ; + } + mSessionProcessor = null; + } + + if (mExtensionClientId >= 0) { + CameraExtensionCharacteristics.unregisterClient(mExtensionClientId); + } + + for (ImageReader reader : mReaderMap.values()) { + reader.close(); + } + mReaderMap.clear(); + + mClientRepeatingRequestSurface = null; + mClientCaptureSurface = null; + } + } + + private void notifyConfigurationFailure() { + synchronized (mInterfaceLock) { + if (mInitialized) { + return; + } + } + + release(); + + final long ident = Binder.clearCallingIdentity(); + try { + mExecutor.execute( + () -> mCallbacks.onConfigureFailed( + CameraAdvancedExtensionSessionImpl.this)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private class SessionStateHandler extends + android.hardware.camera2.CameraCaptureSession.StateCallback { + @Override + public void onClosed(@NonNull CameraCaptureSession session) { + release(); + + final long ident = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallbacks.onClosed( + CameraAdvancedExtensionSessionImpl.this)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public void onConfigureFailed(@NonNull CameraCaptureSession session) { + notifyConfigurationFailure(); + } + + @Override + public void onConfigured(@NonNull CameraCaptureSession session) { + boolean status = true; + synchronized (mInterfaceLock) { + mCaptureSession = session; + try { + mSessionProcessor.onCaptureSessionStart(mRequestProcessor); + mInitialized = true; + } catch (RemoteException e) { + Log.e(TAG, "Failed to start capture session," + + " extension service does not respond!"); + status = false; + session.close(); + } + } + + if (status) { + final long ident = Binder.clearCallingIdentity(); + try { + mExecutor.execute( + () -> mCallbacks.onConfigured(CameraAdvancedExtensionSessionImpl.this)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } else { + notifyConfigurationFailure(); + } + } + } + + private final class RequestCallbackHandler extends ICaptureCallback.Stub { + private final CaptureRequest mClientRequest; + private final Executor mClientExecutor; + private final ExtensionCaptureCallback mClientCallbacks; + + private RequestCallbackHandler(@NonNull CaptureRequest clientRequest, + @NonNull Executor clientExecutor, + @NonNull ExtensionCaptureCallback clientCallbacks) { + mClientRequest = clientRequest; + mClientExecutor = clientExecutor; + mClientCallbacks = clientCallbacks; + } + + @Override + public void onCaptureStarted(int captureSequenceId, long timestamp) { + final long ident = Binder.clearCallingIdentity(); + try { + mClientExecutor.execute( + () -> mClientCallbacks.onCaptureStarted( + CameraAdvancedExtensionSessionImpl.this, mClientRequest, + timestamp)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public void onCaptureProcessStarted(int captureSequenceId) { + final long ident = Binder.clearCallingIdentity(); + try { + mClientExecutor.execute( + () -> mClientCallbacks.onCaptureProcessStarted( + CameraAdvancedExtensionSessionImpl.this, mClientRequest)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public void onCaptureFailed(int captureSequenceId) { + final long ident = Binder.clearCallingIdentity(); + try { + mClientExecutor.execute( + () -> mClientCallbacks.onCaptureFailed( + CameraAdvancedExtensionSessionImpl.this, mClientRequest)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public void onCaptureSequenceCompleted(int captureSequenceId) { + final long ident = Binder.clearCallingIdentity(); + try { + mClientExecutor.execute( + () -> mClientCallbacks.onCaptureSequenceCompleted( + CameraAdvancedExtensionSessionImpl.this, captureSequenceId)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public void onCaptureSequenceAborted(int captureSequenceId) { + final long ident = Binder.clearCallingIdentity(); + try { + mClientExecutor.execute( + () -> mClientCallbacks.onCaptureSequenceAborted( + CameraAdvancedExtensionSessionImpl.this, captureSequenceId)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + private final class CaptureCallbackHandler extends CameraCaptureSession.CaptureCallback { + private final IRequestCallback mCallback; + + public CaptureCallbackHandler(IRequestCallback callback) { + mCallback = callback; + } + + @Override + public void onCaptureBufferLost(CameraCaptureSession session, CaptureRequest request, + Surface target, long frameNumber) { + try { + if (request.getTag() instanceof Integer) { + Integer requestId = (Integer) request.getTag(); + mCallback.onCaptureBufferLost(requestId, frameNumber, + mSurfaceIdMap.get(target)); + } else { + Log.e(TAG, "Invalid capture request tag!"); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify lost capture buffer, extension service doesn't" + + " respond!"); + } + } + + @Override + public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, + TotalCaptureResult result) { + try { + if (request.getTag() instanceof Integer) { + Integer requestId = (Integer) request.getTag(); + mCallback.onCaptureCompleted(requestId, initializeParcelable(result)); + } else { + Log.e(TAG, "Invalid capture request tag!"); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify capture result, extension service doesn't" + + " respond!"); + } + } + + @Override + public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, + CaptureFailure failure) { + try { + if (request.getTag() instanceof Integer) { + Integer requestId = (Integer) request.getTag(); + android.hardware.camera2.extension.CaptureFailure captureFailure = + new android.hardware.camera2.extension.CaptureFailure(); + captureFailure.request = request; + captureFailure.reason = failure.getReason(); + captureFailure.errorPhysicalCameraId = failure.getPhysicalCameraId(); + captureFailure.frameNumber = failure.getFrameNumber(); + captureFailure.sequenceId = failure.getSequenceId(); + captureFailure.dropped = !failure.wasImageCaptured(); + mCallback.onCaptureFailed(requestId, captureFailure); + } else { + Log.e(TAG, "Invalid capture request tag!"); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify capture failure, extension service doesn't" + + " respond!"); + } + } + + @Override + public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request, + CaptureResult partialResult) { + try { + if (request.getTag() instanceof Integer) { + Integer requestId = (Integer) request.getTag(); + mCallback.onCaptureProgressed(requestId, initializeParcelable(partialResult)); + } else { + Log.e(TAG, "Invalid capture request tag!"); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify capture partial result, extension service doesn't" + + " respond!"); + } + } + + @Override + public void onCaptureSequenceAborted(CameraCaptureSession session, int sequenceId) { + try { + mCallback.onCaptureSequenceAborted(sequenceId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify aborted sequence, extension service doesn't" + + " respond!"); + } + } + + @Override + public void onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId, + long frameNumber) { + try { + mCallback.onCaptureSequenceCompleted(sequenceId, frameNumber); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify sequence complete, extension service doesn't" + + " respond!"); + } + } + + @Override + public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, + long timestamp, long frameNumber) { + try { + if (request.getTag() instanceof Integer) { + Integer requestId = (Integer) request.getTag(); + mCallback.onCaptureStarted(requestId, frameNumber, timestamp); + } else { + Log.e(TAG, "Invalid capture request tag!"); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify capture started, extension service doesn't" + + " respond!"); + } + } + } + + private static final class ImageReaderHandler implements ImageReader.OnImageAvailableListener { + private final OutputConfigId mOutputConfigId; + private final IImageProcessorImpl mIImageProcessor; + + private ImageReaderHandler(int outputConfigId, + IImageProcessorImpl iImageProcessor) { + mOutputConfigId = new OutputConfigId(); + mOutputConfigId.id = outputConfigId; + mIImageProcessor = iImageProcessor; + } + + @Override + public void onImageAvailable(ImageReader reader) { + if (mIImageProcessor == null) { + return; + } + + Image img; + try { + img = reader.acquireNextImage(); + } catch (IllegalStateException e) { + Log.e(TAG, "Failed to acquire image, too many images pending!"); + return; + } + if (img == null) { + Log.e(TAG, "Invalid image!"); + return; + } + + try { + reader.detachImage(img); + } catch(Exception e) { + Log.e(TAG, "Failed to detach image"); + img.close(); + return; + } + + ParcelImage parcelImage = new ParcelImage(); + parcelImage.buffer = img.getHardwareBuffer(); + if (img.getFenceFd() >= 0) { + try { + parcelImage.fence = ParcelFileDescriptor.fromFd(img.getFenceFd()); + } catch (IOException e) { + Log.e(TAG,"Failed to parcel buffer fence!"); + } + } + parcelImage.format = img.getFormat(); + parcelImage.timestamp = img.getTimestamp(); + parcelImage.transform = img.getTransform(); + parcelImage.scalingMode = img.getScalingMode(); + parcelImage.planeCount = img.getPlaneCount(); + parcelImage.crop = img.getCropRect(); + + try { + mIImageProcessor.onNextImageAvailable(mOutputConfigId, parcelImage); + } catch (RemoteException e) { + Log.e(TAG, "Failed to propagate image buffer on output surface id: " + + mOutputConfigId + " extension service does not respond!"); + } finally { + parcelImage.buffer.close(); + img.close(); + } + } + } + + private final class RequestProcessor extends IRequestProcessorImpl.Stub { + @Override + public void setImageProcessor(OutputConfigId outputConfigId, + IImageProcessorImpl imageProcessor) { + synchronized (mInterfaceLock) { + if (mReaderMap.containsKey(outputConfigId.id)) { + mReaderMap.get(outputConfigId.id).setOnImageAvailableListener( + new ImageReaderHandler(outputConfigId.id, imageProcessor), mHandler); + } else { + Log.e(TAG, "ImageReader with output config id: " + outputConfigId.id + + " not found!"); + } + } + } + + @Override + public boolean submit(Request request, IRequestCallback callback) { + ArrayList captureList = new ArrayList<>(); + captureList.add(request); + return submitBurst(captureList, callback); + } + + @Override + public boolean submitBurst(List requests, IRequestCallback callback) { + synchronized (mInterfaceLock) { + try { + CaptureCallbackHandler captureCallback = new CaptureCallbackHandler(callback); + ArrayList captureRequests = new ArrayList<>(); + for (Request request : requests) { + captureRequests.add(initializeCaptureRequest(mCameraDevice, request, + mSurfaceIdMap)); + } + mCaptureSession.captureBurstRequests(captureRequests, + new CameraExtensionUtils.HandlerExecutor(mHandler), captureCallback); + } catch (CameraAccessException e) { + Log.e(TAG, "Failed to submit capture requests!"); + return false; + } catch (IllegalStateException e) { + Log.e(TAG, "Capture session closed!"); + } + } + + return true; + } + + @Override + public boolean setRepeating(Request request, IRequestCallback callback) { + synchronized (mInterfaceLock) { + try { + CaptureRequest repeatingRequest = initializeCaptureRequest(mCameraDevice, + request, mSurfaceIdMap); + CaptureCallbackHandler captureCallback = new CaptureCallbackHandler(callback); + mCaptureSession.setSingleRepeatingRequest(repeatingRequest, + new CameraExtensionUtils.HandlerExecutor(mHandler), captureCallback); + } catch (CameraAccessException e) { + Log.e(TAG, "Failed to enable repeating request!"); + return false; + } catch (IllegalStateException e) { + Log.e(TAG, "Capture session closed!"); + } + } + + return true; + } + + @Override + public void abortCaptures() { + synchronized (mInterfaceLock) { + try { + mCaptureSession.abortCaptures(); + } catch (CameraAccessException e) { + Log.e(TAG, "Failed during capture abort!"); + } catch (IllegalStateException e) { + Log.e(TAG, "Capture session closed!"); + } + } + } + + @Override + public void stopRepeating() { + synchronized (mInterfaceLock) { + try { + mCaptureSession.stopRepeating(); + } catch (CameraAccessException e) { + Log.e(TAG, "Failed during repeating capture stop!"); + } catch (IllegalStateException e) { + Log.e(TAG, "Capture session closed!"); + } + } + } + } + + private static CaptureRequest initializeCaptureRequest(CameraDevice cameraDevice, + Request request, HashMap surfaceIdMap) throws CameraAccessException { + CaptureRequest.Builder builder = cameraDevice.createCaptureRequest(request.templateId); + for (OutputConfigId configId : request.targetOutputConfigIds) { + boolean found = false; + for (Map.Entry entry : surfaceIdMap.entrySet()) { + if (entry.getValue() == configId.id) { + builder.addTarget(entry.getKey()); + found = true; + break; + } + } + + if (!found) { + Log.e(TAG, "Surface with output id: " + configId.id + + " not found among registered camera outputs!"); + } + } + + builder.setTag(request.requestId); + CaptureRequest ret = builder.build(); + CameraMetadataNative.update(ret.getNativeMetadata(), request.parameters); + return ret; + } +} diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index b578bf80c2315..11b137ca01f34 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -25,6 +25,7 @@ import android.hardware.ICameraService; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraExtensionCharacteristics; import android.hardware.camera2.CameraExtensionSession; import android.hardware.camera2.CameraOfflineSession; import android.hardware.camera2.CameraDevice; @@ -138,6 +139,7 @@ public class CameraDeviceImpl extends CameraDevice private CameraCaptureSessionCore mCurrentSession; private CameraExtensionSessionImpl mCurrentExtensionSession; + private CameraAdvancedExtensionSessionImpl mCurrentAdvancedExtensionSession; private int mNextSessionId = 0; private final int mAppTargetSdkVersion; @@ -1343,6 +1345,12 @@ public class CameraDeviceImpl extends CameraDevice mCurrentExtensionSession.release(); mCurrentExtensionSession = null; } + + if (mCurrentAdvancedExtensionSession != null) { + mCurrentAdvancedExtensionSession.release(); + mCurrentAdvancedExtensionSession = null; + } + // Only want to fire the onClosed callback once; // either a normal close where the remote device is valid // or a close after a startup error (no remote device but in error state) @@ -2395,9 +2403,14 @@ public class CameraDeviceImpl extends CameraDevice public void createExtensionSession(ExtensionSessionConfiguration extensionConfiguration) throws CameraAccessException { try { - mCurrentExtensionSession = CameraExtensionSessionImpl.createCameraExtensionSession(this, - mContext, - extensionConfiguration); + if (CameraExtensionCharacteristics.areAdvancedExtensionsSupported()) { + mCurrentAdvancedExtensionSession = + CameraAdvancedExtensionSessionImpl.createCameraAdvancedExtensionSession( + this, mContext, extensionConfiguration); + } else { + mCurrentExtensionSession = CameraExtensionSessionImpl.createCameraExtensionSession( + this, mContext, extensionConfiguration); + } } catch (RemoteException e) { throw new CameraAccessException(CameraAccessException.CAMERA_ERROR); } diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java index 936734b0c711e..3b1cb94a6619e 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java @@ -16,6 +16,9 @@ package android.hardware.camera2.impl; +import static android.hardware.camera2.impl.CameraExtensionUtils.JPEG_DEFAULT_QUALITY; +import static android.hardware.camera2.impl.CameraExtensionUtils.JPEG_DEFAULT_ROTATION; + import android.annotation.NonNull; import android.graphics.ImageFormat; import android.hardware.camera2.CaptureResult; @@ -42,8 +45,6 @@ import java.util.concurrent.ConcurrentLinkedQueue; public class CameraExtensionJpegProcessor implements ICaptureProcessorImpl { public final static String TAG = "CameraExtensionJpeg"; private final static int JPEG_QUEUE_SIZE = 1; - private final static int JPEG_DEFAULT_QUALITY = 100; - private final static int JPEG_DEFAULT_ROTATION = 0; private final Handler mHandler; private final HandlerThread mHandlerThread; diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java index 3d771c01e8ac6..5339f41ecdd67 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -110,79 +110,10 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { // Lock to synchronize cross-thread access to device public interface final Object mInterfaceLock = new Object(); // access from this class and Session only! - private static class SurfaceInfo { - public int mWidth = 0; - public int mHeight = 0; - public int mFormat = PixelFormat.RGBA_8888; - public long mUsage = 0; - } - - private static final int SUPPORTED_CAPTURE_OUTPUT_FORMATS[] = { - CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT, - ImageFormat.JPEG - }; - - private static int nativeGetSurfaceWidth(Surface surface) { - return SurfaceUtils.getSurfaceSize(surface).getWidth(); - } - - private static int nativeGetSurfaceHeight(Surface surface) { - return SurfaceUtils.getSurfaceSize(surface).getHeight(); - } - private static int nativeGetSurfaceFormat(Surface surface) { return SurfaceUtils.getSurfaceFormat(surface); } - private static Surface getBurstCaptureSurface( - @NonNull List outputConfigs, - @NonNull HashMap> supportedCaptureSizes) { - for (OutputConfiguration config : outputConfigs) { - SurfaceInfo surfaceInfo = querySurface(config.getSurface()); - for (int supportedFormat : SUPPORTED_CAPTURE_OUTPUT_FORMATS) { - if (surfaceInfo.mFormat == supportedFormat) { - Size captureSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight); - if (supportedCaptureSizes.containsKey(supportedFormat)) { - if (supportedCaptureSizes.get(surfaceInfo.mFormat).contains(captureSize)) { - return config.getSurface(); - } else { - throw new IllegalArgumentException("Capture size not supported!"); - } - } - return config.getSurface(); - } - } - } - - return null; - } - - private static @Nullable Surface getRepeatingRequestSurface( - @NonNull List outputConfigs, - @Nullable List supportedPreviewSizes) { - for (OutputConfiguration config : outputConfigs) { - SurfaceInfo surfaceInfo = querySurface(config.getSurface()); - if ((surfaceInfo.mFormat == - CameraExtensionCharacteristics.NON_PROCESSING_INPUT_FORMAT) || - // The default RGBA_8888 is also implicitly supported because camera will - // internally override it to - // 'CameraExtensionCharacteristics.NON_PROCESSING_INPUT_FORMAT' - (surfaceInfo.mFormat == PixelFormat.RGBA_8888)) { - Size repeatingRequestSurfaceSize = new Size(surfaceInfo.mWidth, - surfaceInfo.mHeight); - if ((supportedPreviewSizes == null) || - (!supportedPreviewSizes.contains(repeatingRequestSurfaceSize))) { - throw new IllegalArgumentException("Repeating request surface size " + - repeatingRequestSurfaceSize + " not supported!"); - } - - return config.getSurface(); - } - } - - return null; - } - /** * @hide */ @@ -221,22 +152,22 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { int suitableSurfaceCount = 0; List supportedPreviewSizes = extensionChars.getExtensionSupportedSizes( config.getExtension(), SurfaceTexture.class); - Surface repeatingRequestSurface = getRepeatingRequestSurface( + Surface repeatingRequestSurface = CameraExtensionUtils.getRepeatingRequestSurface( config.getOutputConfigurations(), supportedPreviewSizes); if (repeatingRequestSurface != null) { suitableSurfaceCount++; } HashMap> supportedCaptureSizes = new HashMap<>(); - for (int format : SUPPORTED_CAPTURE_OUTPUT_FORMATS) { + for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) { List supportedSizes = extensionChars.getExtensionSupportedSizes( config.getExtension(), format); if (supportedSizes != null) { supportedCaptureSizes.put(format, supportedSizes); } } - Surface burstCaptureSurface = getBurstCaptureSurface(config.getOutputConfigurations(), - supportedCaptureSizes); + Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface( + config.getOutputConfigurations(), supportedCaptureSizes); if (burstCaptureSurface != null) { suitableSurfaceCount++; } @@ -266,15 +197,15 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { return session; } - private CameraExtensionSessionImpl(@NonNull IImageCaptureExtenderImpl imageExtender, - @NonNull IPreviewExtenderImpl previewExtender, - @NonNull List previewSizes, - long extensionClientId, - @NonNull CameraDevice cameraDevice, - @Nullable Surface repeatingRequestSurface, - @Nullable Surface burstCaptureSurface, - @NonNull StateCallback callback, - @NonNull Executor executor) { + public CameraExtensionSessionImpl(@NonNull IImageCaptureExtenderImpl imageExtender, + @NonNull IPreviewExtenderImpl previewExtender, + @NonNull List previewSizes, + long extensionClientId, + @NonNull CameraDevice cameraDevice, + @Nullable Surface repeatingRequestSurface, + @Nullable Surface burstCaptureSurface, + @NonNull StateCallback callback, + @NonNull Executor executor) { mExtensionClientId = extensionClientId; mImageExtender = imageExtender; mPreviewExtender = previewExtender; @@ -290,57 +221,17 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { mInitialized = false; } - private static @NonNull SurfaceInfo querySurface(@NonNull Surface s) { - ImageWriter writer = null; - Image img = null; - SurfaceInfo surfaceInfo = new SurfaceInfo(); - int nativeFormat = nativeGetSurfaceFormat(s); - int dataspace = SurfaceUtils.getSurfaceDataspace(s); - // Jpeg surfaces cannot be queried for their usage and other parameters - // in the usual way below. A buffer can only be de-queued after the - // producer overrides the surface dimensions to (width*height) x 1. - if ((nativeFormat == StreamConfigurationMap.HAL_PIXEL_FORMAT_BLOB) && - (dataspace == StreamConfigurationMap.HAL_DATASPACE_V0_JFIF)) { - surfaceInfo.mFormat = ImageFormat.JPEG; - surfaceInfo.mWidth = nativeGetSurfaceWidth(s); - surfaceInfo.mHeight = nativeGetSurfaceHeight(s); - return surfaceInfo; - } - - HardwareBuffer buffer = null; - try { - writer = ImageWriter.newInstance(s, 1); - img = writer.dequeueInputImage(); - buffer = img.getHardwareBuffer(); - surfaceInfo.mFormat = buffer.getFormat(); - surfaceInfo.mWidth = buffer.getWidth(); - surfaceInfo.mHeight = buffer.getHeight(); - surfaceInfo.mUsage = buffer.getUsage(); - } catch (Exception e) { - Log.e(TAG, "Failed to query surface, returning defaults!"); - } finally { - if (buffer != null) { - buffer.close(); - } - if (img != null) { - img.close(); - } - if (writer != null) { - writer.close(); - } - } - - return surfaceInfo; - } - private void initializeRepeatingRequestPipeline() throws RemoteException { - SurfaceInfo repeatingSurfaceInfo = new SurfaceInfo(); + CameraExtensionUtils.SurfaceInfo repeatingSurfaceInfo = + new CameraExtensionUtils.SurfaceInfo(); mPreviewProcessorType = mPreviewExtender.getProcessorType(); if (mClientRepeatingRequestSurface != null) { - repeatingSurfaceInfo = querySurface(mClientRepeatingRequestSurface); + repeatingSurfaceInfo = CameraExtensionUtils.querySurface( + mClientRepeatingRequestSurface); } else { // Make the intermediate surface behave as any regular 'SurfaceTexture' - SurfaceInfo captureSurfaceInfo = querySurface(mClientCaptureSurface); + CameraExtensionUtils.SurfaceInfo captureSurfaceInfo = CameraExtensionUtils.querySurface( + mClientCaptureSurface); Size captureSize = new Size(captureSurfaceInfo.mWidth, captureSurfaceInfo.mHeight); Size previewSize = findSmallestAspectMatchedSize(mSupportedPreviewSizes, captureSize); repeatingSurfaceInfo.mWidth = previewSize.getWidth(); @@ -418,7 +309,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { if (mImageProcessor != null) { if (mClientCaptureSurface != null) { - SurfaceInfo surfaceInfo = querySurface(mClientCaptureSurface); + CameraExtensionUtils.SurfaceInfo surfaceInfo = CameraExtensionUtils.querySurface( + mClientCaptureSurface); if (surfaceInfo.mFormat == ImageFormat.JPEG) { mImageJpegProcessor = new CameraExtensionJpegProcessor(mImageProcessor); mImageProcessor = mImageJpegProcessor; @@ -501,7 +393,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { SessionConfiguration sessionConfig = new SessionConfiguration( SessionConfiguration.SESSION_REGULAR, outputList, - new HandlerExecutor(mHandler), + new CameraExtensionUtils.HandlerExecutor(mHandler), new SessionStateHandler()); if (!sessionParamsList.isEmpty()) { @@ -656,7 +548,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { throw new UnsupportedOperationException("Failed to create still capture burst request"); } - return mCaptureSession.captureBurstRequests(burstRequest, new HandlerExecutor(mHandler), + return mCaptureSession.captureBurstRequests(burstRequest, + new CameraExtensionUtils.HandlerExecutor(mHandler), new BurstRequestHandler(request, executor, listener, requestMap, mBurstCaptureImageCallback)); } @@ -724,7 +617,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { CaptureRequest repeatingRequest = createRequest(mCameraDevice, captureStageList, mCameraRepeatingSurface, CameraDevice.TEMPLATE_PREVIEW); return mCaptureSession.setSingleRepeatingRequest(repeatingRequest, - new HandlerExecutor(mHandler), requestHandler); + new CameraExtensionUtils.HandlerExecutor(mHandler), requestHandler); } /** @hide */ @@ -1705,23 +1598,6 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { return ret; } - private final class HandlerExecutor implements Executor { - private final Handler mHandler; - - public HandlerExecutor(Handler handler) { - mHandler = handler; - } - - @Override - public void execute(Runnable runCmd) { - try { - mHandler.post(runCmd); - } catch (RejectedExecutionException e) { - Log.w(TAG, "Handler thread unavailable, skipping message!"); - } - } - } - private static ParcelImage initializeParcelImage(Image img) { ParcelImage parcelImage = new ParcelImage(); parcelImage.buffer = img.getHardwareBuffer(); diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java new file mode 100644 index 0000000000000..950d716b05e89 --- /dev/null +++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2021 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.impl; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.ImageFormat; +import android.graphics.PixelFormat; +import android.hardware.HardwareBuffer; +import android.hardware.camera2.CameraExtensionCharacteristics; +import android.hardware.camera2.params.OutputConfiguration; +import android.hardware.camera2.params.StreamConfigurationMap; +import android.hardware.camera2.utils.SurfaceUtils; +import android.media.Image; +import android.media.ImageWriter; +import android.os.Handler; +import android.util.Log; +import android.util.Size; +import android.view.Surface; + +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; + +public final class CameraExtensionUtils { + private static final String TAG = "CameraExtensionUtils"; + + public final static int JPEG_DEFAULT_QUALITY = 100; + public final static int JPEG_DEFAULT_ROTATION = 0; + + public static final int[] SUPPORTED_CAPTURE_OUTPUT_FORMATS = { + CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT, + ImageFormat.JPEG + }; + + public static class SurfaceInfo { + public int mWidth = 0; + public int mHeight = 0; + public int mFormat = PixelFormat.RGBA_8888; + public long mUsage = 0; + } + + public static final class HandlerExecutor implements Executor { + private final Handler mHandler; + + public HandlerExecutor(Handler handler) { + mHandler = handler; + } + + @Override + public void execute(Runnable runCmd) { + try { + mHandler.post(runCmd); + } catch (RejectedExecutionException e) { + Log.w(TAG, "Handler thread unavailable, skipping message!"); + } + } + } + + public static @NonNull SurfaceInfo querySurface(@NonNull Surface s) { + ImageWriter writer = null; + Image img = null; + SurfaceInfo surfaceInfo = new SurfaceInfo(); + int nativeFormat = SurfaceUtils.getSurfaceFormat(s); + int dataspace = SurfaceUtils.getSurfaceDataspace(s); + // Jpeg surfaces cannot be queried for their usage and other parameters + // in the usual way below. A buffer can only be de-queued after the + // producer overrides the surface dimensions to (width*height) x 1. + if ((nativeFormat == StreamConfigurationMap.HAL_PIXEL_FORMAT_BLOB) && + (dataspace == StreamConfigurationMap.HAL_DATASPACE_V0_JFIF)) { + surfaceInfo.mFormat = ImageFormat.JPEG; + Size surfaceSize = SurfaceUtils.getSurfaceSize(s); + surfaceInfo.mWidth = surfaceSize.getWidth(); + surfaceInfo.mHeight = surfaceSize.getHeight(); + return surfaceInfo; + } + + HardwareBuffer buffer = null; + try { + writer = ImageWriter.newInstance(s, 1); + img = writer.dequeueInputImage(); + buffer = img.getHardwareBuffer(); + surfaceInfo.mFormat = buffer.getFormat(); + surfaceInfo.mWidth = buffer.getWidth(); + surfaceInfo.mHeight = buffer.getHeight(); + surfaceInfo.mUsage = buffer.getUsage(); + } catch (Exception e) { + Log.e(TAG, "Failed to query surface, returning defaults!"); + } finally { + if (buffer != null) { + buffer.close(); + } + if (img != null) { + img.close(); + } + if (writer != null) { + writer.close(); + } + } + + return surfaceInfo; + } + + public static Surface getBurstCaptureSurface( + @NonNull List outputConfigs, + @NonNull HashMap> supportedCaptureSizes) { + for (OutputConfiguration config : outputConfigs) { + SurfaceInfo surfaceInfo = querySurface(config.getSurface()); + for (int supportedFormat : SUPPORTED_CAPTURE_OUTPUT_FORMATS) { + if (surfaceInfo.mFormat == supportedFormat) { + Size captureSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight); + if (supportedCaptureSizes.containsKey(supportedFormat)) { + if (supportedCaptureSizes.get(surfaceInfo.mFormat).contains(captureSize)) { + return config.getSurface(); + } else { + throw new IllegalArgumentException("Capture size not supported!"); + } + } + return config.getSurface(); + } + } + } + + return null; + } + + public static @Nullable Surface getRepeatingRequestSurface( + @NonNull List outputConfigs, + @Nullable List supportedPreviewSizes) { + for (OutputConfiguration config : outputConfigs) { + SurfaceInfo surfaceInfo = querySurface(config.getSurface()); + if ((surfaceInfo.mFormat == + CameraExtensionCharacteristics.NON_PROCESSING_INPUT_FORMAT) || + // The default RGBA_8888 is also implicitly supported because camera will + // internally override it to + // 'CameraExtensionCharacteristics.NON_PROCESSING_INPUT_FORMAT' + (surfaceInfo.mFormat == PixelFormat.RGBA_8888)) { + Size repeatingRequestSurfaceSize = new Size(surfaceInfo.mWidth, + surfaceInfo.mHeight); + if ((supportedPreviewSizes == null) || + (!supportedPreviewSizes.contains(repeatingRequestSurfaceSize))) { + throw new IllegalArgumentException("Repeating request surface size " + + repeatingRequestSurfaceSize + " not supported!"); + } + + return config.getSurface(); + } + } + + return null; + } +} diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp index 315ca2ed5b476..5c9999dc0c851 100644 --- a/core/jni/android_hardware_camera2_CameraMetadata.cpp +++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp @@ -270,7 +270,28 @@ static void CameraMetadata_update(JNIEnv *env, jclass thiz, jlong dst, jlong src metadataSrc->unlock(metaBuffer); return; } - metadataDst->update(entry.tag, entry.data.u8, entry.count); + switch (entry.type) { + case TYPE_BYTE: + metadataDst->update(entry.tag, entry.data.u8, entry.count); + break; + case TYPE_INT32: + metadataDst->update(entry.tag, entry.data.i32, entry.count); + break; + case TYPE_FLOAT: + metadataDst->update(entry.tag, entry.data.f, entry.count); + break; + case TYPE_INT64: + metadataDst->update(entry.tag, entry.data.i64, entry.count); + break; + case TYPE_DOUBLE: + metadataDst->update(entry.tag, entry.data.d, entry.count); + break; + case TYPE_RATIONAL: + metadataDst->update(entry.tag, entry.data.r, entry.count); + break; + default: + ALOGE("%s: Unsupported tag type: %d!", __func__, entry.type); + } } metadataSrc->unlock(metaBuffer); } diff --git a/packages/services/CameraExtensionsProxy/AndroidManifest.xml b/packages/services/CameraExtensionsProxy/AndroidManifest.xml index 7416cbacc3252..e5f460e593b32 100644 --- a/packages/services/CameraExtensionsProxy/AndroidManifest.xml +++ b/packages/services/CameraExtensionsProxy/AndroidManifest.xml @@ -2,8 +2,6 @@ - - mCharacteristicsHashMap = new HashMap<>(); private static boolean checkForExtensions() { try { @@ -223,7 +265,7 @@ public class CameraExtensionsProxyService extends Service { public long registerClient(Context ctx) { synchronized (mLock) { - if (LATEST_VERSION_SUPPORTED) { + if (LEGACY_VERSION_SUPPORTED) { if (mActiveClients.isEmpty()) { InitializerFuture status = new InitializerFuture(); InitializerImpl.init(EXTENSIONS_VERSION, ctx, new InitializeHandler(status), @@ -257,7 +299,7 @@ public class CameraExtensionsProxyService extends Service { public void unregisterClient(long clientId) { synchronized (mLock) { if (mActiveClients.remove(clientId) && mActiveClients.isEmpty() && - LATEST_VERSION_SUPPORTED) { + LEGACY_VERSION_SUPPORTED) { InitializerFuture status = new InitializerFuture(); InitializerImpl.deinit(new ReleaseHandler(status), new HandlerExecutor(mHandler)); @@ -321,17 +363,39 @@ public class CameraExtensionsProxyService extends Service { } } + /** + * @hide + */ + public static AdvancedExtenderImpl initializeAdvancedExtensionImpl(int extensionType) { + switch (extensionType) { + case CameraExtensionCharacteristics.EXTENSION_AUTOMATIC: + return new AutoAdvancedExtenderImpl(); + case CameraExtensionCharacteristics.EXTENSION_BEAUTY: + return new BeautyAdvancedExtenderImpl(); + case CameraExtensionCharacteristics.EXTENSION_BOKEH: + return new BokehAdvancedExtenderImpl(); + case CameraExtensionCharacteristics.EXTENSION_HDR: + return new HdrAdvancedExtenderImpl(); + case CameraExtensionCharacteristics.EXTENSION_NIGHT: + return new NightAdvancedExtenderImpl(); + default: + throw new IllegalArgumentException("Unknown extension: " + extensionType); + } + } + @Override public void onCreate() { super.onCreate(); // This will setup the camera vendor tag descriptor in the service process + // along with all camera characteristics. try { CameraManager manager = getSystemService(CameraManager.class); String [] cameraIds = manager.getCameraIdListNoLazy(); if (cameraIds != null) { for (String cameraId : cameraIds) { - CameraCharacteristics ch = manager.getCameraCharacteristics(cameraId); + mCharacteristicsHashMap.put(cameraId, + manager.getCameraCharacteristics(cameraId)); } } } catch (CameraAccessException e) { @@ -372,6 +436,29 @@ public class CameraExtensionsProxyService extends Service { return ret; } + private static List initializeParcelable( + Map> sizes) { + if (sizes == null) { + return null; + } + ArrayList ret = new ArrayList<>(sizes.size()); + for (Map.Entry> entry : sizes.entrySet()) { + SizeList sizeList = new SizeList(); + sizeList.format = entry.getKey(); + sizeList.sizes = new ArrayList<>(); + for (android.util.Size size : entry.getValue()) { + android.hardware.camera2.extension.Size sz = + new android.hardware.camera2.extension.Size(); + sz.width = size.getWidth(); + sz.height = size.getHeight(); + sizeList.sizes.add(sz); + } + ret.add(sizeList); + } + + return ret; + } + private static CameraMetadataNative initializeParcelableMetadata( List> paramList) { if (paramList == null) { @@ -386,6 +473,20 @@ public class CameraExtensionsProxyService extends Service { return ret; } + private static CameraMetadataNative initializeParcelableMetadata( + Map, Object> paramMap) { + if (paramMap == null) { + return null; + } + + CameraMetadataNative ret = new CameraMetadataNative(); + for (Map.Entry, Object> param : paramMap.entrySet()) { + ret.set(((CaptureRequest.Key) param.getKey()), param.getValue()); + } + + return ret; + } + private static android.hardware.camera2.extension.CaptureStageImpl initializeParcelable( androidx.camera.extensions.impl.CaptureStageImpl captureStage) { if (captureStage == null) { @@ -400,6 +501,20 @@ public class CameraExtensionsProxyService extends Service { return ret; } + private Request initializeParcelable(RequestProcessorImpl.Request request, int requestId) { + Request ret = new Request(); + ret.targetOutputConfigIds = new ArrayList<>(); + for (int id : request.getTargetOutputConfigIds()) { + OutputConfigId configId = new OutputConfigId(); + configId.id = id; + ret.targetOutputConfigIds.add(configId); + } + ret.templateId = request.getTemplateId(); + ret.parameters = initializeParcelableMetadata(request.getParameters()); + ret.requestId = requestId; + return ret; + } + private class CameraExtensionsProxyServiceStub extends ICameraExtensionsProxyService.Stub { @Override public long registerClient() { @@ -411,6 +526,23 @@ public class CameraExtensionsProxyService extends Service { CameraExtensionsProxyService.unregisterClient(clientId); } + @Override + public boolean advancedExtensionsSupported() { + return ADVANCED_VERSION_SUPPORTED; + } + + @Override + public IAdvancedExtenderImpl initializeAdvancedExtension(int extensionType) { + AdvancedExtenderImpl extension; + try { + extension = initializeAdvancedExtensionImpl(extensionType); + } catch (IllegalArgumentException e) { + return null; + } + + return new AdvancedExtenderImplStub(extension); + } + @Override public IPreviewExtenderImpl initializePreviewExtension(int extensionType) { Pair extension; @@ -436,6 +568,475 @@ public class CameraExtensionsProxyService extends Service { } } + private class AdvancedExtenderImplStub extends IAdvancedExtenderImpl.Stub { + private final AdvancedExtenderImpl mAdvancedExtender; + + public AdvancedExtenderImplStub(AdvancedExtenderImpl advancedExtender) { + mAdvancedExtender = advancedExtender; + } + + @Override + public boolean isExtensionAvailable(String cameraId) { + return mAdvancedExtender.isExtensionAvailable(cameraId, mCharacteristicsHashMap); + } + + @Override + public void init(String cameraId) { + mAdvancedExtender.init(cameraId, mCharacteristicsHashMap); + } + + @Override + public List getSupportedPreviewOutputResolutions(String cameraId) { + Map> supportedSizesMap = + mAdvancedExtender.getSupportedPreviewOutputResolutions(cameraId); + if (supportedSizesMap != null) { + return initializeParcelable(supportedSizesMap); + } + + return null; + } + + @Override + public List getSupportedCaptureOutputResolutions(String cameraId) { + Map> supportedSizesMap = + mAdvancedExtender.getSupportedCaptureOutputResolutions(cameraId); + if (supportedSizesMap != null) { + return initializeParcelable(supportedSizesMap); + } + + return null; + } + + @Override + public LatencyRange getEstimatedCaptureLatencyRange(String cameraId, + android.hardware.camera2.extension.Size outputSize, int format) { + Size sz = new Size(outputSize.width, outputSize.height); + Range latencyRange = mAdvancedExtender.getEstimatedCaptureLatencyRange(cameraId, + sz, format); + if (latencyRange != null) { + LatencyRange ret = new LatencyRange(); + ret.min = latencyRange.getLower(); + ret.max = latencyRange.getUpper(); + return ret; + } + + return null; + } + + @Override + public ISessionProcessorImpl getSessionProcessor() { + return new SessionProcessorImplStub(mAdvancedExtender.createSessionProcessor()); + } + } + + private class CaptureCallbackStub implements SessionProcessorImpl.CaptureCallback { + private final ICaptureCallback mCaptureCallback; + + private CaptureCallbackStub(ICaptureCallback captureCallback) { + mCaptureCallback = captureCallback; + } + + @Override + public void onCaptureStarted(int captureSequenceId, long timestamp) { + if (mCaptureCallback != null) { + try { + mCaptureCallback.onCaptureStarted(captureSequenceId, timestamp); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify capture start due to remote " + + "exception!"); + } + } + } + + @Override + public void onCaptureProcessStarted(int captureSequenceId) { + if (mCaptureCallback != null) { + try { + mCaptureCallback.onCaptureProcessStarted(captureSequenceId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify capture process start due to remote " + + "exception!"); + } + } + } + + @Override + public void onCaptureFailed(int captureSequenceId) { + if (mCaptureCallback != null) { + try { + mCaptureCallback.onCaptureFailed(captureSequenceId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify capture failure due to remote " + + "exception!"); + } + } + } + + @Override + public void onCaptureSequenceCompleted(int captureSequenceId) { + if (mCaptureCallback != null) { + try { + mCaptureCallback.onCaptureSequenceCompleted(captureSequenceId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify capture sequence end due to remote " + + "exception!"); + } + } + } + + @Override + public void onCaptureSequenceAborted(int captureSequenceId) { + if (mCaptureCallback != null) { + try { + mCaptureCallback.onCaptureSequenceAborted(captureSequenceId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify capture sequence abort due to remote " + + "exception!"); + } + } + } + } + + private class RequestCallbackStub extends IRequestCallback.Stub { + private final List mRequests; + private final RequestProcessorImpl.Callback mCallback; + + public RequestCallbackStub(List requests, + RequestProcessorImpl.Callback callback) { + mCallback = callback; + if (mCallback != null) { + mRequests = requests; + } else { + Log.w(TAG, "No valid request callbacks!"); + mRequests = new ArrayList<>(); + } + } + + @Override + public void onCaptureStarted(int requestId, long frameNumber, long timestamp) { + if (mCallback != null) { + if (mRequests.get(requestId) != null) { + mCallback.onCaptureStarted(mRequests.get(requestId), frameNumber, timestamp); + } else { + Log.e(TAG,"Request id: " + requestId + " not found!"); + } + } + } + + @Override + public void onCaptureProgressed(int requestId, ParcelCaptureResult partialResult) { + if (mCallback != null) { + if (mRequests.get(requestId) != null) { + CaptureResult result = new CaptureResult(partialResult.cameraId, + partialResult.results, partialResult.parent, partialResult.sequenceId, + partialResult.frameNumber); + mCallback.onCaptureProgressed(mRequests.get(requestId), result); + } else { + Log.e(TAG,"Request id: " + requestId + " not found!"); + } + } + } + + @Override + public void onCaptureCompleted(int requestId, ParcelTotalCaptureResult totalCaptureResult) { + if (mCallback != null) { + if (mRequests.get(requestId) != null) { + PhysicalCaptureResultInfo[] physicalResults = new PhysicalCaptureResultInfo[0]; + if ((totalCaptureResult.physicalResult != null) && + (!totalCaptureResult.physicalResult.isEmpty())) { + int count = totalCaptureResult.physicalResult.size(); + physicalResults = new PhysicalCaptureResultInfo[count]; + physicalResults = totalCaptureResult.physicalResult.toArray( + physicalResults); + } + ArrayList partials = new ArrayList<>( + totalCaptureResult.partials.size()); + for (ParcelCaptureResult parcelResult : totalCaptureResult.partials) { + partials.add(new CaptureResult(parcelResult.cameraId, parcelResult.results, + parcelResult.parent, parcelResult.sequenceId, + parcelResult.frameNumber)); + } + TotalCaptureResult result = new TotalCaptureResult( + totalCaptureResult.logicalCameraId, totalCaptureResult.results, + totalCaptureResult.parent, totalCaptureResult.sequenceId, + totalCaptureResult.frameNumber, partials, totalCaptureResult.sessionId, + physicalResults); + mCallback.onCaptureCompleted(mRequests.get(requestId), result); + } else { + Log.e(TAG,"Request id: " + requestId + " not found!"); + } + } + } + + @Override + public void onCaptureFailed(int requestId, CaptureFailure captureFailure) { + if (mCallback != null) { + if (mRequests.get(requestId) != null) { + android.hardware.camera2.CaptureFailure failure = + new android.hardware.camera2.CaptureFailure(captureFailure.request, + captureFailure.reason, captureFailure.dropped, + captureFailure.sequenceId, captureFailure.frameNumber, + captureFailure.errorPhysicalCameraId); + mCallback.onCaptureFailed(mRequests.get(requestId), failure); + } else { + Log.e(TAG,"Request id: " + requestId + " not found!"); + } + } + } + + @Override + public void onCaptureBufferLost(int requestId, long frameNumber, int outputStreamId) { + if (mCallback != null) { + if (mRequests.get(requestId) != null) { + mCallback.onCaptureBufferLost(mRequests.get(requestId), frameNumber, + outputStreamId); + } else { + Log.e(TAG,"Request id: " + requestId + " not found!"); + } + } + } + + @Override + public void onCaptureSequenceCompleted(int sequenceId, long frameNumber) { + if (mCallback != null) { + mCallback.onCaptureSequenceCompleted(sequenceId, frameNumber); + } + } + + @Override + public void onCaptureSequenceAborted(int sequenceId) { + if (mCallback != null) { + mCallback.onCaptureSequenceAborted(sequenceId); + } + } + } + + private class ImageProcessorImplStub extends IImageProcessorImpl.Stub { + private final ImageProcessorImpl mImageProcessor; + + public ImageProcessorImplStub(ImageProcessorImpl imageProcessor) { + mImageProcessor = imageProcessor; + } + + @Override + public void onNextImageAvailable(OutputConfigId outputConfigId, ParcelImage img) { + if (mImageProcessor != null) { + mImageProcessor.onNextImageAvailable(outputConfigId.id, img.timestamp, + new ImageReferenceImpl(img)); + } + } + } + + private class RequestProcessorStub implements RequestProcessorImpl { + private final IRequestProcessorImpl mRequestProcessor; + + public RequestProcessorStub(IRequestProcessorImpl requestProcessor) { + mRequestProcessor = requestProcessor; + } + + @Override + public void setImageProcessor(int outputConfigId, + ImageProcessorImpl imageProcessor) { + OutputConfigId configId = new OutputConfigId(); + configId.id = outputConfigId; + try { + mRequestProcessor.setImageProcessor(configId, + new ImageProcessorImplStub(imageProcessor)); + } catch (RemoteException e) { + Log.e(TAG, "Failed to set image processor due to remote exception!"); + } + } + + @Override + public boolean submit(Request request, Callback callback) { + ArrayList requests = new ArrayList<>(); + requests.add(request); + return submit(requests, callback); + } + + @Override + public boolean submit(List requests, Callback callback) { + ArrayList captureRequests = + new ArrayList<>(); + int requestId = 0; + for (Request request : requests) { + captureRequests.add(initializeParcelable(request, requestId)); + requestId++; + } + + try { + return mRequestProcessor.submitBurst(captureRequests, + new RequestCallbackStub(requests, callback)); + } catch (RemoteException e) { + Log.e(TAG, "Failed to submit request due to remote exception!"); + } + return false; + } + + @Override + public boolean setRepeating(Request request, Callback callback) { + try { + ArrayList requests = new ArrayList<>(); + requests.add(request); + return mRequestProcessor.setRepeating(initializeParcelable(request, 0), + new RequestCallbackStub(requests, callback)); + } catch (RemoteException e) { + Log.e(TAG, "Failed to submit repeating request due to remote exception!"); + } + + return false; + } + + @Override + public void abortCaptures() { + try { + mRequestProcessor.abortCaptures(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to abort requests due to remote exception!"); + } + } + + @Override + public void stopRepeating() { + try { + mRequestProcessor.stopRepeating(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to stop repeating request due to remote exception!"); + } + } + } + + private class SessionProcessorImplStub extends ISessionProcessorImpl.Stub { + private final SessionProcessorImpl mSessionProcessor; + + public SessionProcessorImplStub(SessionProcessorImpl sessionProcessor) { + mSessionProcessor = sessionProcessor; + } + + @Override + public CameraSessionConfig initSession(String cameraId, OutputSurface previewSurface, + OutputSurface burstSurface) { + OutputSurfaceImplStub outputPreviewSurfaceImpl = + new OutputSurfaceImplStub(previewSurface); + OutputSurfaceImplStub outputBurstSurfaceImpl = + new OutputSurfaceImplStub(burstSurface); + + Camera2SessionConfigImpl sessionConfig = mSessionProcessor.initSession(cameraId, + mCharacteristicsHashMap, getApplicationContext(), outputPreviewSurfaceImpl, + outputBurstSurfaceImpl, null /*imageAnalysisSurfaceConfig*/); + + List outputConfigs = sessionConfig.getOutputConfigs(); + CameraSessionConfig ret = new CameraSessionConfig(); + ret.outputConfigs = new ArrayList<>(); + for (Camera2OutputConfigImpl output : outputConfigs) { + CameraOutputConfig entry = new CameraOutputConfig(); + entry.outputId = new OutputConfigId(); + entry.outputId.id = output.getId(); + entry.physicalCameraId = output.getPhysicalCameraId(); + entry.surfaceGroupId = output.getSurfaceGroupId(); + if (output instanceof SurfaceOutputConfigImpl) { + SurfaceOutputConfigImpl surfaceConfig = (SurfaceOutputConfigImpl) output; + entry.type = CameraOutputConfig.TYPE_SURFACE; + entry.surface = surfaceConfig.getSurface(); + } else if (output instanceof ImageReaderOutputConfigImpl) { + ImageReaderOutputConfigImpl imageReaderOutputConfig = + (ImageReaderOutputConfigImpl) output; + entry.type = CameraOutputConfig.TYPE_IMAGEREADER; + entry.size = new android.hardware.camera2.extension.Size(); + entry.size.width = imageReaderOutputConfig.getSize().getWidth(); + entry.size.height = imageReaderOutputConfig.getSize().getHeight(); + entry.imageFormat = imageReaderOutputConfig.getImageFormat(); + entry.capacity = imageReaderOutputConfig.getMaxImages(); + } else if (output instanceof MultiResolutionImageReaderOutputConfigImpl) { + MultiResolutionImageReaderOutputConfigImpl multiResReaderConfig = + (MultiResolutionImageReaderOutputConfigImpl) output; + entry.type = CameraOutputConfig.TYPE_MULTIRES_IMAGEREADER; + entry.imageFormat = multiResReaderConfig.getImageFormat(); + entry.capacity = multiResReaderConfig.getMaxImages(); + } else { + throw new IllegalStateException("Unknown output config type!"); + } + List sharedOutputs = + output.getSurfaceSharingOutputConfigs(); + if ((sharedOutputs != null) && (!sharedOutputs.isEmpty())) { + entry.surfaceSharingOutputConfigs = new ArrayList<>(); + for (Camera2OutputConfigImpl sharedOutput : sharedOutputs) { + OutputConfigId outputId = new OutputConfigId(); + outputId.id = sharedOutput.getId(); + entry.surfaceSharingOutputConfigs.add(outputId); + } + } + ret.outputConfigs.add(entry); + } + ret.sessionTemplateId = sessionConfig.getSessionTemplateId(); + ret.sessionParameter = initializeParcelableMetadata( + sessionConfig.getSessionParameters()); + + return ret; + } + + @Override + public void deInitSession() { + mSessionProcessor.deInitSession(); + } + + @Override + public void onCaptureSessionStart(IRequestProcessorImpl requestProcessor) { + mSessionProcessor.onCaptureSessionStart(new RequestProcessorStub(requestProcessor)); + } + + @Override + public void onCaptureSessionEnd() { + mSessionProcessor.onCaptureSessionEnd(); + } + + @Override + public int startRepeating(ICaptureCallback callback) { + return mSessionProcessor.startRepeating(new CaptureCallbackStub(callback)); + } + + @Override + public void stopRepeating() { + mSessionProcessor.stopRepeating(); + } + + @Override + public int startCapture(ICaptureCallback callback, int jpegRotation, int jpegQuality) { + HashMap, Object> paramMap = new HashMap<>(); + paramMap.put(CaptureRequest.JPEG_ORIENTATION, jpegRotation); + paramMap.put(CaptureRequest.JPEG_QUALITY, jpegQuality); + mSessionProcessor.setParameters(paramMap); + return mSessionProcessor.startCapture(new CaptureCallbackStub(callback)); + } + } + + private class OutputSurfaceImplStub implements OutputSurfaceImpl { + private final Surface mSurface; + private final Size mSize; + private final int mImageFormat; + + public OutputSurfaceImplStub(OutputSurface outputSurface) { + mSurface = outputSurface.surface; + mSize = new Size(outputSurface.size.width, outputSurface.size.height); + mImageFormat = outputSurface.imageFormat; + } + + @Override + public Surface getSurface() { + return mSurface; + } + + @Override + public Size getSize() { + return mSize; + } + + @Override + public int getImageFormat() { + return mImageFormat; + } + } + private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub { private final PreviewExtenderImpl mPreviewExtender; @@ -471,7 +1072,7 @@ public class CameraExtensionsProxyService extends Service { @Override public void init(String cameraId, CameraMetadataNative chars) { - if (LATEST_VERSION_SUPPORTED) { + if (LEGACY_VERSION_SUPPORTED) { mPreviewExtender.init(cameraId, new CameraCharacteristics(chars)); } } @@ -535,7 +1136,7 @@ public class CameraExtensionsProxyService extends Service { @Override public List getSupportedResolutions() { - if (LATEST_VERSION_SUPPORTED) { + if (LEGACY_VERSION_SUPPORTED) { List> sizes = mPreviewExtender.getSupportedResolutions(); if ((sizes != null) && !sizes.isEmpty()) { @@ -581,7 +1182,7 @@ public class CameraExtensionsProxyService extends Service { @Override public void init(String cameraId, CameraMetadataNative chars) { - if (LATEST_VERSION_SUPPORTED) { + if (LEGACY_VERSION_SUPPORTED) { mImageExtender.init(cameraId, new CameraCharacteristics(chars)); } } @@ -627,7 +1228,7 @@ public class CameraExtensionsProxyService extends Service { @Override public List getSupportedResolutions() { - if (LATEST_VERSION_SUPPORTED) { + if (LEGACY_VERSION_SUPPORTED) { List> sizes = mImageExtender.getSupportedResolutions(); if ((sizes != null) && !sizes.isEmpty()) { @@ -737,6 +1338,51 @@ public class CameraExtensionsProxyService extends Service { } } + private class ImageReferenceImpl extends ExtensionImage + implements androidx.camera.extensions.impl.advanced.ImageReferenceImpl { + + private final Object mImageLock = new Object(); + private int mReferenceCount; + + private ImageReferenceImpl(ParcelImage parcelImage) { + super(parcelImage); + mReferenceCount = 1; + } + + @Override + public boolean increment() { + synchronized (mImageLock) { + if (mReferenceCount <= 0) { + return false; + } + mReferenceCount++; + } + + return true; + } + + @Override + public boolean decrement() { + synchronized (mImageLock) { + if (mReferenceCount <= 0) { + return false; + } + mReferenceCount--; + + if (mReferenceCount <= 0) { + close(); + } + } + + return true; + } + + @Override + public Image get() { + return this; + } + } + private class ExtensionImage extends android.media.Image { private final android.hardware.camera2.extension.ParcelImage mParcelImage; private GraphicBuffer mGraphicBuffer;