CaptureCollector.java revision 971251f362410caebe8eeb5b6c13d8912f72d50e
1/*
2 * Copyright (C) 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 */
16package android.hardware.camera2.legacy;
17
18import android.hardware.camera2.impl.CameraMetadataNative;
19import android.util.Log;
20import android.util.Pair;
21
22import java.util.ArrayDeque;
23import java.util.concurrent.TimeUnit;
24import java.util.concurrent.locks.Condition;
25import java.util.concurrent.locks.ReentrantLock;
26
27/**
28 * Collect timestamps and state for each {@link CaptureRequest} as it passes through
29 * the Legacy camera pipeline.
30 */
31public class CaptureCollector {
32    private static final String TAG = "CaptureCollector";
33
34    private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
35
36    private static final int FLAG_RECEIVED_JPEG = 1;
37    private static final int FLAG_RECEIVED_JPEG_TS = 2;
38    private static final int FLAG_RECEIVED_PREVIEW = 4;
39    private static final int FLAG_RECEIVED_PREVIEW_TS = 8;
40    private static final int FLAG_RECEIVED_ALL_JPEG = FLAG_RECEIVED_JPEG | FLAG_RECEIVED_JPEG_TS;
41    private static final int FLAG_RECEIVED_ALL_PREVIEW = FLAG_RECEIVED_PREVIEW |
42            FLAG_RECEIVED_PREVIEW_TS;
43
44    private static final int MAX_JPEGS_IN_FLIGHT = 1;
45
46    private class CaptureHolder {
47        private final RequestHolder mRequest;
48        private final LegacyRequest mLegacy;
49        public final boolean needsJpeg;
50        public final boolean needsPreview;
51
52        private long mTimestamp = 0;
53        private int mReceivedFlags = 0;
54        private boolean mHasStarted = false;
55
56        public CaptureHolder(RequestHolder request, LegacyRequest legacyHolder) {
57            mRequest = request;
58            mLegacy = legacyHolder;
59            needsJpeg = request.hasJpegTargets();
60            needsPreview = request.hasPreviewTargets();
61        }
62
63        public boolean isPreviewCompleted() {
64            return (mReceivedFlags & FLAG_RECEIVED_ALL_PREVIEW) == FLAG_RECEIVED_ALL_PREVIEW;
65        }
66
67        public  boolean isJpegCompleted() {
68            return (mReceivedFlags & FLAG_RECEIVED_ALL_JPEG) == FLAG_RECEIVED_ALL_JPEG;
69        }
70
71        public boolean isCompleted() {
72            return (needsJpeg == isJpegCompleted()) && (needsPreview == isPreviewCompleted());
73        }
74
75        public void tryComplete() {
76            if (isCompleted()) {
77                if (needsPreview && isPreviewCompleted()) {
78                    CaptureCollector.this.onPreviewCompleted();
79                }
80                CaptureCollector.this.onRequestCompleted(mRequest, mLegacy, mTimestamp);
81            }
82        }
83
84        public void setJpegTimestamp(long timestamp) {
85            if (DEBUG) {
86                Log.d(TAG, "setJpegTimestamp - called for request " + mRequest.getRequestId());
87            }
88            if (!needsJpeg) {
89                throw new IllegalStateException(
90                        "setJpegTimestamp called for capture with no jpeg targets.");
91            }
92            if (isCompleted()) {
93                throw new IllegalStateException(
94                        "setJpegTimestamp called on already completed request.");
95            }
96
97            mReceivedFlags |= FLAG_RECEIVED_JPEG_TS;
98
99            if (mTimestamp == 0) {
100                mTimestamp = timestamp;
101            }
102
103            if (!mHasStarted) {
104                mHasStarted = true;
105                CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp);
106            }
107
108            tryComplete();
109        }
110
111        public void setJpegProduced() {
112            if (DEBUG) {
113                Log.d(TAG, "setJpegProduced - called for request " + mRequest.getRequestId());
114            }
115            if (!needsJpeg) {
116                throw new IllegalStateException(
117                        "setJpegProduced called for capture with no jpeg targets.");
118            }
119            if (isCompleted()) {
120                throw new IllegalStateException(
121                        "setJpegProduced called on already completed request.");
122            }
123
124            mReceivedFlags |= FLAG_RECEIVED_JPEG;
125            tryComplete();
126        }
127
128        public void setPreviewTimestamp(long timestamp) {
129            if (DEBUG) {
130                Log.d(TAG, "setPreviewTimestamp - called for request " + mRequest.getRequestId());
131            }
132            if (!needsPreview) {
133                throw new IllegalStateException(
134                        "setPreviewTimestamp called for capture with no preview targets.");
135            }
136            if (isCompleted()) {
137                throw new IllegalStateException(
138                        "setPreviewTimestamp called on already completed request.");
139            }
140
141            mReceivedFlags |= FLAG_RECEIVED_PREVIEW_TS;
142
143            if (mTimestamp == 0) {
144                mTimestamp = timestamp;
145            }
146
147            if (!needsJpeg) {
148                if (!mHasStarted) {
149                    mHasStarted = true;
150                    CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp);
151                }
152            }
153
154            tryComplete();
155        }
156
157        public void setPreviewProduced() {
158            if (DEBUG) {
159                Log.d(TAG, "setPreviewProduced - called for request " + mRequest.getRequestId());
160            }
161            if (!needsPreview) {
162                throw new IllegalStateException(
163                        "setPreviewProduced called for capture with no preview targets.");
164            }
165            if (isCompleted()) {
166                throw new IllegalStateException(
167                        "setPreviewProduced called on already completed request.");
168            }
169
170            mReceivedFlags |= FLAG_RECEIVED_PREVIEW;
171            tryComplete();
172        }
173    }
174
175    private final ArrayDeque<CaptureHolder> mJpegCaptureQueue;
176    private final ArrayDeque<CaptureHolder> mJpegProduceQueue;
177    private final ArrayDeque<CaptureHolder> mPreviewCaptureQueue;
178    private final ArrayDeque<CaptureHolder> mPreviewProduceQueue;
179
180    private final ReentrantLock mLock = new ReentrantLock();
181    private final Condition mIsEmpty;
182    private final Condition mPreviewsEmpty;
183    private final Condition mNotFull;
184    private final CameraDeviceState mDeviceState;
185    private final LegacyResultMapper mMapper = new LegacyResultMapper();
186    private int mInFlight = 0;
187    private int mInFlightPreviews = 0;
188    private final int mMaxInFlight;
189
190    /**
191     * Create a new {@link CaptureCollector} that can modify the given {@link CameraDeviceState}.
192     *
193     * @param maxInFlight max allowed in-flight requests.
194     * @param deviceState the {@link CameraDeviceState} to update as requests are processed.
195     */
196    public CaptureCollector(int maxInFlight, CameraDeviceState deviceState) {
197        mMaxInFlight = maxInFlight;
198        mJpegCaptureQueue = new ArrayDeque<>(MAX_JPEGS_IN_FLIGHT);
199        mJpegProduceQueue = new ArrayDeque<>(MAX_JPEGS_IN_FLIGHT);
200        mPreviewCaptureQueue = new ArrayDeque<>(mMaxInFlight);
201        mPreviewProduceQueue = new ArrayDeque<>(mMaxInFlight);
202        mIsEmpty = mLock.newCondition();
203        mNotFull = mLock.newCondition();
204        mPreviewsEmpty = mLock.newCondition();
205        mDeviceState = deviceState;
206    }
207
208    /**
209     * Queue a new request.
210     *
211     * <p>
212     * For requests that use the Camera1 API preview output stream, this will block if there are
213     * already {@code maxInFlight} requests in progress (until at least one prior request has
214     * completed). For requests that use the Camera1 API jpeg callbacks, this will block until
215     * all prior requests have been completed to avoid stopping preview for
216     * {@link android.hardware.Camera#takePicture} before prior preview requests have been
217     * completed.
218     * </p>
219     * @param holder the {@link RequestHolder} for this request.
220     * @param legacy the {@link LegacyRequest} for this request; this will not be mutated.
221     * @param timeout a timeout to use for this call.
222     * @param unit the units to use for the timeout.
223     * @return {@code false} if this method timed out.
224     * @throws InterruptedException if this thread is interrupted.
225     */
226    public boolean queueRequest(RequestHolder holder, LegacyRequest legacy, long timeout,
227                                TimeUnit unit)
228            throws InterruptedException {
229        CaptureHolder h = new CaptureHolder(holder, legacy);
230        long nanos = unit.toNanos(timeout);
231        final ReentrantLock lock = this.mLock;
232        lock.lock();
233        try {
234            if (DEBUG) {
235                Log.d(TAG, "queueRequest  for request " + holder.getRequestId() +
236                        " - " + mInFlight + " requests remain in flight.");
237            }
238            if (h.needsJpeg) {
239                // Wait for all current requests to finish before queueing jpeg.
240                while (mInFlight > 0) {
241                    if (nanos <= 0) {
242                        return false;
243                    }
244                    nanos = mIsEmpty.awaitNanos(nanos);
245                }
246                mJpegCaptureQueue.add(h);
247                mJpegProduceQueue.add(h);
248            }
249            if (h.needsPreview) {
250                while (mInFlight >= mMaxInFlight) {
251                    if (nanos <= 0) {
252                        return false;
253                    }
254                    nanos = mNotFull.awaitNanos(nanos);
255                }
256                mPreviewCaptureQueue.add(h);
257                mPreviewProduceQueue.add(h);
258                mInFlightPreviews++;
259            }
260
261            if (!(h.needsJpeg || h.needsPreview)) {
262                throw new IllegalStateException("Request must target at least one output surface!");
263            }
264
265            mInFlight++;
266            return true;
267        } finally {
268            lock.unlock();
269        }
270    }
271
272    /**
273     * Wait all queued requests to complete.
274     *
275     * @param timeout a timeout to use for this call.
276     * @param unit the units to use for the timeout.
277     * @return {@code false} if this method timed out.
278     * @throws InterruptedException if this thread is interrupted.
279     */
280    public boolean waitForEmpty(long timeout, TimeUnit unit) throws InterruptedException {
281        long nanos = unit.toNanos(timeout);
282        final ReentrantLock lock = this.mLock;
283        lock.lock();
284        try {
285            while (mInFlight > 0) {
286                if (nanos <= 0) {
287                    return false;
288                }
289                nanos = mIsEmpty.awaitNanos(nanos);
290            }
291            return true;
292        } finally {
293            lock.unlock();
294        }
295    }
296
297    /**
298     * Wait all queued requests that use the Camera1 API preview output to complete.
299     *
300     * @param timeout a timeout to use for this call.
301     * @param unit the units to use for the timeout.
302     * @return {@code false} if this method timed out.
303     * @throws InterruptedException if this thread is interrupted.
304     */
305    public boolean waitForPreviewsEmpty(long timeout, TimeUnit unit) throws InterruptedException {
306        long nanos = unit.toNanos(timeout);
307        final ReentrantLock lock = this.mLock;
308        lock.lock();
309        try {
310            while (mInFlightPreviews > 0) {
311                if (nanos <= 0) {
312                    return false;
313                }
314                nanos = mPreviewsEmpty.awaitNanos(nanos);
315            }
316            return true;
317        } finally {
318            lock.unlock();
319        }
320    }
321
322    /**
323     * Called to alert the {@link CaptureCollector} that the jpeg capture has begun.
324     *
325     * @param timestamp the time of the jpeg capture.
326     * @return the {@link RequestHolder} for the request associated with this capture.
327     */
328    public RequestHolder jpegCaptured(long timestamp) {
329        final ReentrantLock lock = this.mLock;
330        lock.lock();
331        try {
332            CaptureHolder h = mJpegCaptureQueue.poll();
333            if (h == null) {
334                Log.w(TAG, "jpegCaptured called with no jpeg request on queue!");
335                return null;
336            }
337            h.setJpegTimestamp(timestamp);
338            return h.mRequest;
339        } finally {
340            lock.unlock();
341        }
342    }
343
344    /**
345     * Called to alert the {@link CaptureCollector} that the jpeg capture has completed.
346     *
347     * @return a pair containing the {@link RequestHolder} and the timestamp of the capture.
348     */
349    public Pair<RequestHolder, Long> jpegProduced() {
350        final ReentrantLock lock = this.mLock;
351        lock.lock();
352        try {
353            CaptureHolder h = mJpegProduceQueue.poll();
354            if (h == null) {
355                Log.w(TAG, "jpegProduced called with no jpeg request on queue!");
356                return null;
357            }
358            h.setJpegProduced();
359            return new Pair<>(h.mRequest, h.mTimestamp);
360        } finally {
361            lock.unlock();
362        }
363    }
364
365    /**
366     * Check if there are any pending capture requests that use the Camera1 API preview output.
367     *
368     * @return {@code true} if there are pending preview requests.
369     */
370    public boolean hasPendingPreviewCaptures() {
371        final ReentrantLock lock = this.mLock;
372        lock.lock();
373        try {
374            return !mPreviewCaptureQueue.isEmpty();
375        } finally {
376            lock.unlock();
377        }
378    }
379
380    /**
381     * Called to alert the {@link CaptureCollector} that the preview capture has begun.
382     *
383     * @param timestamp the time of the preview capture.
384     * @return a pair containing the {@link RequestHolder} and the timestamp of the capture.
385     */
386    public Pair<RequestHolder, Long> previewCaptured(long timestamp) {
387        final ReentrantLock lock = this.mLock;
388        lock.lock();
389        try {
390            CaptureHolder h = mPreviewCaptureQueue.poll();
391            if (h == null) {
392                Log.w(TAG, "previewCaptured called with no preview request on queue!");
393                return null;
394            }
395            h.setPreviewTimestamp(timestamp);
396            return new Pair<>(h.mRequest, h.mTimestamp);
397        } finally {
398            lock.unlock();
399        }
400    }
401
402    /**
403     * Called to alert the {@link CaptureCollector} that the preview capture has completed.
404     *
405     * @return the {@link RequestHolder} for the request associated with this capture.
406     */
407    public RequestHolder previewProduced() {
408        final ReentrantLock lock = this.mLock;
409        lock.lock();
410        try {
411            CaptureHolder h = mPreviewProduceQueue.poll();
412            if (h == null) {
413                Log.w(TAG, "previewProduced called with no preview request on queue!");
414                return null;
415            }
416            h.setPreviewProduced();
417            return h.mRequest;
418        } finally {
419            lock.unlock();
420        }
421    }
422
423    private void onPreviewCompleted() {
424        mInFlightPreviews--;
425        if (mInFlightPreviews < 0) {
426            throw new IllegalStateException(
427                    "More preview captures completed than requests queued.");
428        }
429        if (mInFlightPreviews == 0) {
430            mPreviewsEmpty.signalAll();
431        }
432    }
433
434    private void onRequestCompleted(RequestHolder request, LegacyRequest legacyHolder,
435                                    long timestamp) {
436        mInFlight--;
437        if (DEBUG) {
438            Log.d(TAG, "Completed request " + request.getRequestId() +
439                    ", " + mInFlight + " requests remain in flight.");
440        }
441        if (mInFlight < 0) {
442            throw new IllegalStateException(
443                    "More captures completed than requests queued.");
444        }
445        mNotFull.signalAll();
446        if (mInFlight == 0) {
447            mIsEmpty.signalAll();
448        }
449        CameraMetadataNative result = mMapper.cachedConvertResultMetadata(
450                legacyHolder, timestamp);
451        mDeviceState.setCaptureResult(request, result);
452    }
453}
454