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 */ 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 */ 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 */ 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 * If there are multiple pending buffers, {@link Allocation#ioReceive receive} the latest one. 103 * 104 * <p>Does not block if there are no currently pending buffers.</p> 105 * 106 * @return {@code true} only if any buffers were received. 107 * 108 * @throws IllegalStateException If this object has already been {@link #close closed} 109 */ 110 public synchronized boolean receiveLatestAvailableBuffers() { 111 checkNotClosed(); 112 113 int updatedBuffers = 0; 114 while (mListener.isBufferPending()) { 115 mListener.waitForBuffer(); 116 mAllocation.ioReceive(); 117 updatedBuffers++; 118 } 119 120 if (VERBOSE) Log.v(TAG, "receiveLatestAvailableBuffers - updated = " + updatedBuffers); 121 122 return updatedBuffers > 0; 123 } 124 125 /** 126 * Closes the object and detaches the listener from the {@link Allocation}. 127 * 128 * <p>This has a side effect of calling {@link #receiveLatestAvailableBuffers} 129 * 130 * <p>Does <i>not</i> destroy the underlying {@link Allocation}.</p> 131 */ 132 @Override 133 public synchronized void close() { 134 if (mClosed) return; 135 136 receiveLatestAvailableBuffers(); 137 mAllocation.setOnBufferAvailableListener(/*callback*/null); 138 mClosed = true; 139 } 140 141 protected void checkNotClosed() { 142 if (mClosed) { 143 throw new IllegalStateException(TAG + " has been closed"); 144 } 145 } 146 147 @Override 148 protected void finalize() throws Throwable { 149 try { 150 close(); 151 } finally { 152 super.finalize(); 153 } 154 } 155 156 private BlockingInputAllocation(Allocation allocation) { 157 mAllocation = allocation; 158 159 mListener = new OnBufferAvailableListener(); 160 mAllocation.setOnBufferAvailableListener(mListener); 161 } 162 163 // TODO: refactor with the ImageReader Listener code to use a LinkedBlockingQueue 164 private static class OnBufferAvailableListener implements Allocation.OnBufferAvailableListener { 165 private int mPendingBuffers = 0; 166 private final Object mBufferSyncObject = new Object(); 167 private static final int TIMEOUT_MS = 5000; 168 169 public boolean isBufferPending() { 170 synchronized (mBufferSyncObject) { 171 return (mPendingBuffers > 0); 172 } 173 } 174 175 /** 176 * Waits for a buffer. Caller must call ioReceive exactly once after calling this. 177 * 178 * @throws TimeoutRuntimeException If waiting for the buffer has timed out. 179 */ 180 public void waitForBuffer() { 181 synchronized (mBufferSyncObject) { 182 while (mPendingBuffers == 0) { 183 try { 184 if (VERBOSE) Log.v(TAG, "waiting for next buffer"); 185 mBufferSyncObject.wait(TIMEOUT_MS); 186 if (mPendingBuffers == 0) { 187 throw new TimeoutRuntimeException("wait for buffer image timed out"); 188 } 189 } catch (InterruptedException ie) { 190 throw new AssertionError(ie); 191 } 192 } 193 mPendingBuffers--; 194 } 195 } 196 197 @Override 198 public void onBufferAvailable(Allocation a) { 199 if (VERBOSE) Log.v(TAG, "new buffer in allocation available"); 200 synchronized (mBufferSyncObject) { 201 mPendingBuffers++; 202 mBufferSyncObject.notifyAll(); 203 } 204 } 205 } 206 } 207