| /* |
| * Copyright 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.camera.core; |
| |
| import android.graphics.PixelFormat; |
| import android.graphics.SurfaceTexture; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.util.Log; |
| import android.util.Size; |
| import android.view.Surface; |
| |
| import androidx.annotation.GuardedBy; |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.camera.core.impl.utils.executor.CameraXExecutors; |
| import androidx.camera.core.impl.utils.futures.Futures; |
| |
| import com.google.common.util.concurrent.ListenableFuture; |
| |
| /** |
| * A {@link DeferrableSurface} that does processing and outputs a {@link SurfaceTexture}. |
| */ |
| final class ProcessingSurfaceTexture extends DeferrableSurface implements SurfaceTextureHolder { |
| private static final String TAG = "ProcessingSurfaceTextur"; |
| |
| private final Object mLock = new Object(); |
| |
| // Callback when Image is ready from InputImageReader. |
| private final ImageReaderProxy.OnImageAvailableListener mTransformedListener = |
| new ImageReaderProxy.OnImageAvailableListener() { |
| @Override |
| public void onImageAvailable(ImageReaderProxy reader) { |
| imageIncoming(reader); |
| } |
| }; |
| |
| @SuppressWarnings("WeakerAccess") /* synthetic accessor */ |
| @GuardedBy("mLock") |
| boolean mReleased = false; |
| |
| @NonNull |
| private final Size mResolution; |
| |
| @SuppressWarnings("WeakerAccess") /* synthetic accessor */ |
| @GuardedBy("mLock") |
| final MetadataImageReader mInputImageReader; |
| |
| // The Surface that is backed by mInputImageReader |
| @SuppressWarnings("WeakerAccess") /* synthetic accessor */ |
| @GuardedBy("mLock") |
| final Surface mInputSurface; |
| |
| private final Handler mImageReaderHandler; |
| |
| // Maximum number of images in the input ImageReader |
| private static final int MAX_IMAGES = 2; |
| |
| // The output SurfaceTexture |
| @SuppressWarnings("WeakerAccess") /* synthetic accessor */ |
| @GuardedBy("mLock") |
| SurfaceTexture mSurfaceTexture; |
| |
| // The Surface that is backed by mSurfaceTexture |
| @SuppressWarnings("WeakerAccess") /* synthetic accessor */ |
| @GuardedBy("mLock") |
| Surface mSurfaceTextureSurface; |
| |
| @SuppressWarnings("WeakerAccess") /* synthetic accessor */ |
| final CaptureStage mCaptureStage; |
| |
| @SuppressWarnings("WeakerAccess") /* synthetic accessor */ |
| @NonNull |
| @GuardedBy("mLock") |
| final CaptureProcessor mCaptureProcessor; |
| |
| private final CameraCaptureCallback mCameraCaptureCallback; |
| |
| /** |
| * Create a {@link ProcessingSurfaceTexture} with specific configurations. |
| * |
| * @param width Width of the ImageReader |
| * @param height Height of the ImageReader |
| * @param format Image format |
| * @param handler Handler for executing |
| * {@link ImageReaderProxy.OnImageAvailableListener}. If this is |
| * {@code null} then execution will be done on the calling thread's |
| * {@link Looper}. |
| * @param captureStage The {@link CaptureStage} includes the processing information |
| * @param captureProcessor The {@link CaptureProcessor} to be invoked when the Images are ready |
| */ |
| ProcessingSurfaceTexture(int width, int height, int format, @Nullable Handler handler, |
| @NonNull CaptureStage captureStage, @NonNull CaptureProcessor captureProcessor) { |
| |
| mResolution = new Size(width, height); |
| |
| if (handler != null) { |
| mImageReaderHandler = handler; |
| } else { |
| Looper looper = Looper.myLooper(); |
| |
| if (looper == null) { |
| throw new IllegalStateException( |
| "Creating a ProcessingSurfaceTexture requires a non-null Handler, or be " |
| + "created on a thread with a Looper."); |
| } |
| |
| mImageReaderHandler = new Handler(looper); |
| } |
| |
| // input |
| mInputImageReader = new MetadataImageReader( |
| width, |
| height, |
| format, |
| MAX_IMAGES, |
| mImageReaderHandler); |
| mInputImageReader.setOnImageAvailableListener(mTransformedListener, mImageReaderHandler); |
| mInputSurface = mInputImageReader.getSurface(); |
| mCameraCaptureCallback = mInputImageReader.getCameraCaptureCallback(); |
| |
| // output |
| mSurfaceTexture = FixedSizeSurfaceTextures.createDetachedSurfaceTexture(mResolution); |
| mSurfaceTextureSurface = new Surface(mSurfaceTexture); |
| |
| // processing |
| mCaptureProcessor = captureProcessor; |
| mCaptureProcessor.onOutputSurface(mSurfaceTextureSurface, PixelFormat.RGBA_8888); |
| mCaptureProcessor.onResolutionUpdate(mResolution); |
| mCaptureStage = captureStage; |
| } |
| |
| @Override |
| @NonNull |
| public ListenableFuture<Surface> provideSurface() { |
| return Futures.immediateFuture(mInputSurface); |
| } |
| |
| /** |
| * Returns the SurfaceTexture that the result of the processing gets written to. |
| * |
| * <p> This should only be called by the consumer thread. |
| * |
| * @throws IllegalStateException if {@link #release()} ()} has already been called |
| */ |
| @Override |
| @NonNull |
| public SurfaceTexture getSurfaceTexture() { |
| synchronized (mLock) { |
| if (mReleased) { |
| throw new IllegalStateException("ProcessingSurfaceTexture already released!"); |
| } |
| |
| return mSurfaceTexture; |
| } |
| } |
| |
| /** |
| * Returns necessary camera callbacks to retrieve metadata from camera result. |
| * |
| * @throws IllegalStateException if {@link #release()} has already been called |
| */ |
| @Nullable |
| CameraCaptureCallback getCameraCaptureCallback() { |
| synchronized (mLock) { |
| if (mReleased) { |
| throw new IllegalStateException("ProcessingSurfaceTexture already released!"); |
| } |
| |
| return mCameraCaptureCallback; |
| } |
| } |
| |
| /** |
| * Close the {@link ProcessingSurfaceTexture}. |
| * |
| * <p> After closing the ProcessingSurfaceTexture it should not be used again. A new instance |
| * should be created. This should only be called by the consumer thread. |
| */ |
| @Override |
| public void release() { |
| synchronized (mLock) { |
| if (mReleased) { |
| return; |
| } |
| |
| mSurfaceTexture.release(); |
| mSurfaceTexture = null; |
| mSurfaceTextureSurface.release(); |
| mSurfaceTextureSurface = null; |
| |
| mReleased = true; |
| |
| // Remove the previous listener so that if an image is queued it will not be processed |
| mInputImageReader.setOnImageAvailableListener( |
| new ImageReaderProxy.OnImageAvailableListener() { |
| @Override |
| public void onImageAvailable(ImageReaderProxy imageReaderProxy) { |
| try (ImageProxy image = imageReaderProxy.acquireLatestImage()) { |
| // Do nothing with image since simply emptying the queue |
| } catch (IllegalStateException e) { |
| // This might be thrown because mInputImageReader.close() might be |
| // called on another thread. However, we can ignore because we are |
| // simply emptying the queue. |
| } |
| } |
| }, |
| mImageReaderHandler |
| ); |
| |
| // Need to wait for Surface has been detached before closing it |
| setOnSurfaceDetachedListener(CameraXExecutors.directExecutor(), |
| new OnSurfaceDetachedListener() { |
| @Override |
| public void onSurfaceDetached() { |
| closeInputs(); |
| } |
| }); |
| } |
| } |
| |
| // Inputs need to wait until DeferrableSurfaces are detached before closing |
| @SuppressWarnings("WeakerAccess") |
| void closeInputs() { |
| synchronized (mLock) { |
| mInputImageReader.close(); |
| mInputSurface.release(); |
| } |
| } |
| |
| // Incoming Image from InputImageReader. Acquires it and add to SettableImageProxyBundle. |
| @SuppressWarnings("WeakerAccess") |
| @GuardedBy("mLock") |
| void imageIncoming(ImageReaderProxy imageReader) { |
| if (mReleased) { |
| return; |
| } |
| |
| ImageProxy image = null; |
| try { |
| image = imageReader.acquireNextImage(); |
| } catch (IllegalStateException e) { |
| Log.e(TAG, "Failed to acquire next image.", e); |
| } |
| |
| if (image == null) { |
| return; |
| } |
| |
| ImageInfo imageInfo = image.getImageInfo(); |
| if (imageInfo == null) { |
| image.close(); |
| return; |
| } |
| |
| Object tagObject = imageInfo.getTag(); |
| if (tagObject == null) { |
| image.close(); |
| return; |
| } |
| |
| if (!(tagObject instanceof Integer)) { |
| image.close(); |
| return; |
| } |
| |
| Integer tag = (Integer) tagObject; |
| |
| if (mCaptureStage.getId() != tag) { |
| Log.w(TAG, "ImageProxyBundle does not contain this id: " + tag); |
| image.close(); |
| } else { |
| SingleImageProxyBundle imageProxyBundle = new SingleImageProxyBundle(image); |
| mCaptureProcessor.process(imageProxyBundle); |
| imageProxyBundle.close(); |
| } |
| } |
| } |