| /* |
| * Copyright (C) 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.media.Image; |
| import android.media.ImageReader; |
| import android.view.Surface; |
| |
| import androidx.annotation.GuardedBy; |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.RequiresApi; |
| import androidx.camera.core.impl.ImageReaderProxy; |
| import androidx.camera.core.impl.utils.MainThreadAsyncHandler; |
| |
| import java.util.concurrent.Executor; |
| |
| /** |
| * An {@link ImageReaderProxy} which wraps around an {@link ImageReader}. |
| * |
| * <p>All methods map one-to-one between this {@link ImageReaderProxy} and the wrapped {@link |
| * ImageReader}. |
| */ |
| @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java |
| class AndroidImageReaderProxy implements ImageReaderProxy { |
| @GuardedBy("mLock") |
| private final ImageReader mImageReader; |
| private final Object mLock = new Object(); |
| |
| @GuardedBy("mLock") |
| private boolean mIsImageAvailableListenerCleared = true; |
| |
| /** |
| * Creates a new instance which wraps the given image reader. |
| * |
| * @param imageReader to wrap |
| * @return new {@link AndroidImageReaderProxy} instance |
| */ |
| AndroidImageReaderProxy(ImageReader imageReader) { |
| mImageReader = imageReader; |
| } |
| |
| @Override |
| @Nullable |
| public ImageProxy acquireLatestImage() { |
| synchronized (mLock) { |
| Image image; |
| try { |
| image = mImageReader.acquireLatestImage(); |
| } catch (RuntimeException e) { |
| /* In API level 23 or below, it will throw "java.lang.RuntimeException: |
| ImageReaderContext is not initialized" when ImageReader is closed. To make the |
| behavior consistent as newer API levels, we make it return null Image instead.*/ |
| if (isImageReaderContextNotInitializedException(e)) { |
| image = null; |
| } else { |
| throw e; // only catch RuntimeException:ImageReaderContext is not initialized |
| } |
| } |
| |
| if (image == null) { |
| return null; |
| } |
| return new AndroidImageProxy(image); |
| } |
| } |
| |
| @Override |
| @Nullable |
| public ImageProxy acquireNextImage() { |
| synchronized (mLock) { |
| Image image; |
| try { |
| image = mImageReader.acquireNextImage(); |
| } catch (RuntimeException e) { |
| /* In API level 23 or below, it will throw "java.lang.RuntimeException: |
| ImageReaderContext is not initialized" when ImageReader is closed. To make the |
| behavior consistent as newer API levels, we make it return null Image instead.*/ |
| if (isImageReaderContextNotInitializedException(e)) { |
| image = null; |
| } else { |
| throw e; // only catch RuntimeException:ImageReaderContext is not initialized |
| } |
| } |
| |
| if (image == null) { |
| return null; |
| } |
| return new AndroidImageProxy(image); |
| } |
| } |
| |
| private boolean isImageReaderContextNotInitializedException(RuntimeException e) { |
| return "ImageReaderContext is not initialized".equals(e.getMessage()); |
| } |
| |
| @Override |
| public void close() { |
| synchronized (mLock) { |
| mImageReader.close(); |
| } |
| } |
| |
| @Override |
| public int getHeight() { |
| synchronized (mLock) { |
| return mImageReader.getHeight(); |
| } |
| } |
| |
| @Override |
| public int getWidth() { |
| synchronized (mLock) { |
| return mImageReader.getWidth(); |
| } |
| } |
| |
| @Override |
| public int getImageFormat() { |
| synchronized (mLock) { |
| return mImageReader.getImageFormat(); |
| } |
| } |
| |
| @Override |
| public int getMaxImages() { |
| synchronized (mLock) { |
| return mImageReader.getMaxImages(); |
| } |
| } |
| |
| @Nullable |
| @Override |
| public Surface getSurface() { |
| synchronized (mLock) { |
| return mImageReader.getSurface(); |
| } |
| } |
| |
| @Override |
| public void setOnImageAvailableListener( |
| @NonNull OnImageAvailableListener listener, |
| @NonNull Executor executor) { |
| synchronized (mLock) { |
| mIsImageAvailableListenerCleared = false; |
| // ImageReader does not accept an executor. As a workaround, the callback is run on main |
| // handler then immediately posted to the executor. |
| ImageReader.OnImageAvailableListener transformedListener = (imageReader) -> { |
| synchronized (mLock) { |
| // There might be a timing issue that the listener is executed after |
| // clearOnImageAvailableListener() has been called. Uses a flag to skip the |
| // execution if the listener has actually been cleared. |
| if (!mIsImageAvailableListenerCleared) { |
| executor.execute( |
| () -> listener.onImageAvailable(AndroidImageReaderProxy.this)); |
| } |
| } |
| }; |
| mImageReader.setOnImageAvailableListener(transformedListener, |
| MainThreadAsyncHandler.getInstance()); |
| } |
| } |
| |
| @Override |
| public void clearOnImageAvailableListener() { |
| synchronized (mLock) { |
| mIsImageAvailableListenerCleared = true; |
| mImageReader.setOnImageAvailableListener(null, null); |
| } |
| } |
| } |