1 /* 2 * Copyright 2014 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.hardware.camera2.cts.rs; 18 19 import static android.hardware.camera2.cts.helpers.Preconditions.*; 20 21 import android.hardware.camera2.cts.helpers.UncheckedCloseable; 22 import android.renderscript.Allocation; 23 import android.util.Log; 24 25 import com.android.ex.camera2.exceptions.TimeoutRuntimeException; 26 27 /** 28 * An {@link Allocation} wrapper that can be used to block until new buffers are available. 29 * 30 * <p>Can only be used only with {@link Allocation#USAGE_IO_INPUT} usage Allocations.</p> 31 * 32 * <p>When used with a {@link android.hardware.camera2.CameraDevice CameraDevice} this 33 * must be used as an output surface.</p> 34 */ 35 class BlockingInputAllocation implements UncheckedCloseable { 36 37 private static final String TAG = BlockingInputAllocation.class.getSimpleName(); 38 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 39 40 private final Allocation mAllocation; 41 private final OnBufferAvailableListener mListener; 42 private boolean mClosed; 43 44 /** 45 * Wrap an existing Allocation with this {@link BlockingInputAllocation}. 46 * 47 * <p>Doing this will clear any existing associated buffer listeners and replace 48 * it with a new one.</p> 49 * 50 * @param allocation A non-{@code null} {@link Allocation allocation} 51 * @return a new {@link BlockingInputAllocation} instance 52 * 53 * @throws NullPointerException 54 * If {@code allocation} was {@code null} 55 * @throws IllegalArgumentException 56 * If {@code allocation}'s usage did not have one of USAGE_IO_INPUT or USAGE_IO_OUTPUT 57 * @throws IllegalStateException 58 * If this object has already been {@link #close closed} 59 */ wrap(Allocation allocation)60 public static BlockingInputAllocation wrap(Allocation allocation) { 61 checkNotNull("allocation", allocation); 62 checkBitFlags("usage", allocation.getUsage(), "USAGE_IO_INPUT", Allocation.USAGE_IO_INPUT); 63 64 return new BlockingInputAllocation(allocation); 65 } 66 67 /** 68 * Get the Allocation backing this {@link BlockingInputAllocation}. 69 * 70 * @return Allocation instance (non-{@code null}). 71 * 72 * @throws IllegalStateException If this object has already been {@link #close closed} 73 */ getAllocation()74 public Allocation getAllocation() { 75 checkNotClosed(); 76 77 return mAllocation; 78 } 79 80 /** 81 * Waits for a buffer to become available, then immediately 82 * {@link Allocation#ioReceive receives} it. 83 * 84 * <p>After calling this, the next script used with this allocation will use the 85 * newer buffer.</p> 86 * 87 * @throws TimeoutRuntimeException If waiting for the buffer has timed out. 88 * @throws IllegalStateException If this object has already been {@link #close closed} 89 */ waitForBufferAndReceive()90 public synchronized void waitForBufferAndReceive() { 91 checkNotClosed(); 92 93 if (VERBOSE) Log.v(TAG, "waitForBufferAndReceive - begin"); 94 95 mListener.waitForBuffer(); 96 mAllocation.ioReceive(); 97 98 if (VERBOSE) Log.v(TAG, "waitForBufferAndReceive - Allocation#ioReceive"); 99 } 100 101 /** 102 * Waits for a buffer to become available, then immediately 103 * {@link Allocation#ioReceive receives} it. 104 * 105 * <p>After calling this, the next script used with this allocation will use the 106 * newer buffer.</p> 107 * 108 * @param timeoutMs timeout in milliseconds. 109 * 110 * @throws TimeoutRuntimeException If waiting for the buffer has timed out. 111 * @throws IllegalStateException If this object has already been {@link #close closed} 112 */ waitForBufferAndReceive(long timeoutMs)113 public synchronized void waitForBufferAndReceive(long timeoutMs) { 114 checkNotClosed(); 115 116 if (VERBOSE) Log.v(TAG, "waitForBufferAndReceive - begin"); 117 118 mListener.waitForBuffer(timeoutMs); 119 mAllocation.ioReceive(); 120 121 if (VERBOSE) Log.v(TAG, "waitForBufferAndReceive - Allocation#ioReceive"); 122 } 123 124 /** 125 * If there are multiple pending buffers, {@link Allocation#ioReceive receive} the latest one. 126 * 127 * <p>Does not block if there are no currently pending buffers.</p> 128 * 129 * @return {@code true} only if any buffers were received. 130 * 131 * @throws IllegalStateException If this object has already been {@link #close closed} 132 */ receiveLatestAvailableBuffers()133 public synchronized boolean receiveLatestAvailableBuffers() { 134 checkNotClosed(); 135 136 int updatedBuffers = 0; 137 while (mListener.isBufferPending()) { 138 mListener.waitForBuffer(); 139 mAllocation.ioReceive(); 140 updatedBuffers++; 141 } 142 143 if (VERBOSE) Log.v(TAG, "receiveLatestAvailableBuffers - updated = " + updatedBuffers); 144 145 return updatedBuffers > 0; 146 } 147 148 /** 149 * Closes the object and detaches the listener from the {@link Allocation}. 150 * 151 * <p>This has a side effect of calling {@link #receiveLatestAvailableBuffers} 152 * 153 * <p>Does <i>not</i> destroy the underlying {@link Allocation}.</p> 154 */ 155 @Override close()156 public synchronized void close() { 157 if (mClosed) return; 158 159 receiveLatestAvailableBuffers(); 160 mAllocation.setOnBufferAvailableListener(/*callback*/null); 161 mClosed = true; 162 } 163 checkNotClosed()164 protected void checkNotClosed() { 165 if (mClosed) { 166 throw new IllegalStateException(TAG + " has been closed"); 167 } 168 } 169 170 @Override finalize()171 protected void finalize() throws Throwable { 172 try { 173 close(); 174 } finally { 175 super.finalize(); 176 } 177 } 178 BlockingInputAllocation(Allocation allocation)179 private BlockingInputAllocation(Allocation allocation) { 180 mAllocation = allocation; 181 182 mListener = new OnBufferAvailableListener(); 183 mAllocation.setOnBufferAvailableListener(mListener); 184 } 185 186 // TODO: refactor with the ImageReader Listener code to use a LinkedBlockingQueue 187 private static class OnBufferAvailableListener implements Allocation.OnBufferAvailableListener { 188 private int mPendingBuffers = 0; 189 private final Object mBufferSyncObject = new Object(); 190 private static final int TIMEOUT_MS = 20000; 191 isBufferPending()192 public boolean isBufferPending() { 193 synchronized (mBufferSyncObject) { 194 return (mPendingBuffers > 0); 195 } 196 } 197 198 /** 199 * Waits for a buffer. Caller must call ioReceive exactly once after calling this. 200 * 201 * @param timeoutMs wait timeout in milliseconds 202 * 203 * @throws TimeoutRuntimeException If waiting for the buffer has timed out. 204 */ waitForBufferWithTimeout(long timeoutMs)205 private void waitForBufferWithTimeout(long timeoutMs) { 206 synchronized (mBufferSyncObject) { 207 while (mPendingBuffers == 0) { 208 try { 209 if (VERBOSE) Log.v(TAG, "waiting for next buffer"); 210 mBufferSyncObject.wait(timeoutMs); 211 if (mPendingBuffers == 0) { 212 throw new TimeoutRuntimeException("wait for buffer image timed out"); 213 } 214 } catch (InterruptedException ie) { 215 throw new AssertionError(ie); 216 } 217 } 218 mPendingBuffers--; 219 } 220 } 221 222 /** 223 * Waits for a buffer. Caller must call ioReceive exactly once after calling this. 224 * 225 * @param timeoutMs wait timeout in milliseconds. 226 * 227 * @throws TimeoutRuntimeException If waiting for the buffer has timed out. 228 */ waitForBuffer(long timeoutMs)229 public void waitForBuffer(long timeoutMs) { 230 if (timeoutMs <= TIMEOUT_MS) { 231 waitForBufferWithTimeout(TIMEOUT_MS); 232 } else { 233 waitForBufferWithTimeout(timeoutMs + TIMEOUT_MS); 234 } 235 } 236 237 /** 238 * Waits for a buffer. Caller must call ioReceive exactly once after calling this. 239 * 240 * @throws TimeoutRuntimeException If waiting for the buffer has timed out. 241 */ waitForBuffer()242 public void waitForBuffer() { 243 waitForBufferWithTimeout(TIMEOUT_MS); 244 } 245 246 @Override onBufferAvailable(Allocation a)247 public void onBufferAvailable(Allocation a) { 248 if (VERBOSE) Log.v(TAG, "new buffer in allocation available"); 249 synchronized (mBufferSyncObject) { 250 mPendingBuffers++; 251 mBufferSyncObject.notifyAll(); 252 } 253 } 254 } 255 } 256