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      * 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      */
receiveLatestAvailableBuffers()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
close()133     public synchronized void close() {
134         if (mClosed) return;
135 
136         receiveLatestAvailableBuffers();
137         mAllocation.setOnBufferAvailableListener(/*callback*/null);
138         mClosed = true;
139     }
140 
checkNotClosed()141     protected void checkNotClosed() {
142         if (mClosed) {
143             throw new IllegalStateException(TAG + " has been closed");
144         }
145     }
146 
147     @Override
finalize()148     protected void finalize() throws Throwable {
149         try {
150             close();
151         } finally {
152             super.finalize();
153         }
154     }
155 
BlockingInputAllocation(Allocation allocation)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 
isBufferPending()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          */
waitForBuffer()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
onBufferAvailable(Allocation a)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