1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.mediav2.common.cts; 18 19 import static org.junit.Assert.assertNotNull; 20 import static org.junit.Assert.assertNull; 21 import static org.junit.Assert.assertTrue; 22 23 import android.media.Image; 24 import android.media.ImageReader; 25 import android.os.Handler; 26 import android.os.HandlerThread; 27 import android.util.Log; 28 import android.util.Pair; 29 import android.view.Surface; 30 31 import java.util.ArrayDeque; 32 import java.util.Locale; 33 import java.util.concurrent.TimeUnit; 34 import java.util.concurrent.locks.Condition; 35 import java.util.concurrent.locks.Lock; 36 import java.util.concurrent.locks.ReentrantLock; 37 import java.util.function.Function; 38 39 /** 40 * Wrapper class to hold surface provided by ImageReader. 41 */ 42 public class ImageSurface implements ImageReader.OnImageAvailableListener { 43 private static final String LOG_TAG = ImageSurface.class.getSimpleName(); 44 45 private final ArrayDeque<Pair<Image, Exception>> mQueue = new ArrayDeque<>(); 46 private final Lock mLock = new ReentrantLock(); 47 private final Condition mCondition = mLock.newCondition(); 48 private ImageReader mReader; 49 private Surface mReaderSurface; 50 private HandlerThread mHandlerThread; 51 private Handler mHandler; 52 private int mImageBoundToSurfaceId; 53 private boolean mQueueOverflowed; 54 private Function<ImageAndAttributes, Boolean> mPredicate; 55 56 public static class ImageAndAttributes { 57 public Image mImage; 58 public int mImageBoundToSurfaceId; 59 ImageAndAttributes(Image image, int surfaceId)60 public ImageAndAttributes(Image image, int surfaceId) { 61 mImage = image; 62 mImageBoundToSurfaceId = surfaceId; 63 } 64 } 65 66 @Override onImageAvailable(ImageReader reader)67 public void onImageAvailable(ImageReader reader) { 68 mLock.lock(); 69 try { 70 if (mQueue.size() == reader.getMaxImages()) { 71 Log.w(LOG_TAG, "image queue is at full capacity, releasing oldest image to" 72 + " make space for image just received"); 73 releaseImage(mQueue.poll()); 74 mQueueOverflowed = true; 75 } 76 Image image = reader.acquireNextImage(); 77 Log.d(LOG_TAG, "received image" + image); 78 mQueue.add(Pair.create(image, null /* Exception */)); 79 mCondition.signalAll(); 80 } catch (Exception e) { 81 Log.e(LOG_TAG, "Can't handle Exceptions in onImageAvailable " + e); 82 mQueue.add(Pair.create(null /* Image */, e)); 83 } finally { 84 mLock.unlock(); 85 } 86 } 87 getImage(long timeout)88 public Image getImage(long timeout) throws InterruptedException { 89 int retry = 3; 90 Image image = null; 91 mLock.lock(); 92 try { 93 while (mQueue.size() == 0 && retry > 0) { 94 if (!mCondition.await(timeout, TimeUnit.MILLISECONDS)) { 95 retry--; 96 } 97 } 98 if (mQueue.size() > 0) { 99 Pair<Image, Exception> imageResult = mQueue.poll(); 100 assertNotNull("bad element in image queue", imageResult); 101 image = imageResult.first; 102 Exception e = imageResult.second; 103 assertNull("onImageAvailable() generated an exception: " + e, e); 104 assertNotNull("Wait for an image timed out in " + timeout + "ms", image); 105 } 106 } finally { 107 mLock.unlock(); 108 } 109 return image; 110 } 111 createSurface(int width, int height, int format, int maxNumImages, int surfaceId, Function<ImageAndAttributes, Boolean> predicate)112 public void createSurface(int width, int height, int format, int maxNumImages, 113 int surfaceId, Function<ImageAndAttributes, Boolean> predicate) { 114 if (mReader != null) { 115 throw new RuntimeException( 116 "Current instance of ImageSurface already has a weak reference to some " 117 + "surface, release older surface or reuse it"); 118 } 119 mHandlerThread = new HandlerThread(LOG_TAG); 120 mHandlerThread.start(); 121 mHandler = new Handler(mHandlerThread.getLooper()); 122 mReader = ImageReader.newInstance(width, height, format, maxNumImages); 123 mReader.setOnImageAvailableListener(this, mHandler); 124 mReaderSurface = mReader.getSurface(); 125 mImageBoundToSurfaceId = surfaceId; 126 mQueueOverflowed = false; 127 mPredicate = predicate; 128 Log.v(LOG_TAG, String.format(Locale.getDefault(), "Created ImageReader size (%dx%d)," 129 + " format %d, maxNumImages %d", width, height, format, maxNumImages)); 130 } 131 getSurface()132 public Surface getSurface() { 133 return mReaderSurface; 134 } 135 releaseImage(Pair<Image, Exception> imageResult)136 private void releaseImage(Pair<Image, Exception> imageResult) { 137 assertNotNull("bad element in image queue", imageResult); 138 Image image = imageResult.first; 139 Exception e = imageResult.second; 140 assertNull("onImageAvailable() generated an exception: " + e, e); 141 assertNotNull("received null for image", image); 142 if (mPredicate != null) { 143 assertTrue("predicate failed on image instance", 144 mPredicate.apply(new ImageAndAttributes(image, mImageBoundToSurfaceId))); 145 } 146 image.close(); 147 } 148 hasQueueOverflowed()149 public boolean hasQueueOverflowed() { 150 return mQueueOverflowed; 151 } 152 release()153 public void release() { 154 mReaderSurface = null; 155 if (mReader != null) { 156 mLock.lock(); 157 try { 158 mQueue.forEach(this::releaseImage); 159 mQueue.clear(); 160 Image image = mReader.acquireLatestImage(); 161 if (image != null) { 162 image.close(); 163 } 164 } finally { 165 mReader.close(); 166 mReader = null; 167 mLock.unlock(); 168 } 169 } 170 if (mHandlerThread != null) { 171 mHandlerThread.quitSafely(); 172 mHandlerThread = null; 173 } 174 mHandler = null; 175 } 176 } 177