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