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.CameraDeviceImpl;
19import android.util.Log;
20import android.util.MutableLong;
21import android.util.Pair;
22
23import java.util.ArrayDeque;
24import java.util.ArrayList;
25import java.util.TreeSet;
26import java.util.concurrent.TimeUnit;
27import java.util.concurrent.locks.Condition;
28import java.util.concurrent.locks.ReentrantLock;
29
30/**
31 * Collect timestamps and state for each {@link CaptureRequest} as it passes through
32 * the Legacy camera pipeline.
33 */
34public class CaptureCollector {
35    private static final String TAG = "CaptureCollector";
36
37    private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
38
39    private static final int FLAG_RECEIVED_JPEG = 1;
40    private static final int FLAG_RECEIVED_JPEG_TS = 2;
41    private static final int FLAG_RECEIVED_PREVIEW = 4;
42    private static final int FLAG_RECEIVED_PREVIEW_TS = 8;
43    private static final int FLAG_RECEIVED_ALL_JPEG = FLAG_RECEIVED_JPEG | FLAG_RECEIVED_JPEG_TS;
44    private static final int FLAG_RECEIVED_ALL_PREVIEW = FLAG_RECEIVED_PREVIEW |
45            FLAG_RECEIVED_PREVIEW_TS;
46
47    private static final int MAX_JPEGS_IN_FLIGHT = 1;
48
49    private class CaptureHolder implements Comparable<CaptureHolder>{
50        private final RequestHolder mRequest;
51        private final LegacyRequest mLegacy;
52        public final boolean needsJpeg;
53        public final boolean needsPreview;
54
55        private long mTimestamp = 0;
56        private int mReceivedFlags = 0;
57        private boolean mHasStarted = false;
58        private boolean mFailedJpeg = false;
59        private boolean mFailedPreview = false;
60        private boolean mCompleted = false;
61        private boolean mPreviewCompleted = false;
62
63        public CaptureHolder(RequestHolder request, LegacyRequest legacyHolder) {
64            mRequest = request;
65            mLegacy = legacyHolder;
66            needsJpeg = request.hasJpegTargets();
67            needsPreview = request.hasPreviewTargets();
68        }
69
70        public boolean isPreviewCompleted() {
71            return (mReceivedFlags & FLAG_RECEIVED_ALL_PREVIEW) == FLAG_RECEIVED_ALL_PREVIEW;
72        }
73
74        public  boolean isJpegCompleted() {
75            return (mReceivedFlags & FLAG_RECEIVED_ALL_JPEG) == FLAG_RECEIVED_ALL_JPEG;
76        }
77
78        public boolean isCompleted() {
79            return (needsJpeg == isJpegCompleted()) && (needsPreview == isPreviewCompleted());
80        }
81
82        public void tryComplete() {
83            if (!mPreviewCompleted && needsPreview && isPreviewCompleted()) {
84                CaptureCollector.this.onPreviewCompleted();
85                mPreviewCompleted = true;
86            }
87
88            if (isCompleted() && !mCompleted) {
89                if (mFailedPreview || mFailedJpeg) {
90                    if (!mHasStarted) {
91                        // Send a request error if the capture has not yet started.
92                        mRequest.failRequest();
93                        CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp,
94                                CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_REQUEST);
95                    } else {
96                        // Send buffer dropped errors for each pending buffer if the request has
97                        // started.
98                        if (mFailedPreview) {
99                            Log.w(TAG, "Preview buffers dropped for request: " +
100                                    mRequest.getRequestId());
101                            for (int i = 0; i < mRequest.numPreviewTargets(); i++) {
102                                CaptureCollector.this.mDeviceState.setCaptureResult(mRequest,
103                                    /*result*/null,
104                                        CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_BUFFER);
105                            }
106                        }
107                        if (mFailedJpeg) {
108                            Log.w(TAG, "Jpeg buffers dropped for request: " +
109                                    mRequest.getRequestId());
110                            for (int i = 0; i < mRequest.numJpegTargets(); i++) {
111                                CaptureCollector.this.mDeviceState.setCaptureResult(mRequest,
112                                    /*result*/null,
113                                        CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_BUFFER);
114                            }
115                        }
116                    }
117                }
118                CaptureCollector.this.onRequestCompleted(CaptureHolder.this);
119                mCompleted = true;
120            }
121        }
122
123        public void setJpegTimestamp(long timestamp) {
124            if (DEBUG) {
125                Log.d(TAG, "setJpegTimestamp - called for request " + mRequest.getRequestId());
126            }
127            if (!needsJpeg) {
128                throw new IllegalStateException(
129                        "setJpegTimestamp called for capture with no jpeg targets.");
130            }
131            if (isCompleted()) {
132                throw new IllegalStateException(
133                        "setJpegTimestamp called on already completed request.");
134            }
135
136            mReceivedFlags |= FLAG_RECEIVED_JPEG_TS;
137
138            if (mTimestamp == 0) {
139                mTimestamp = timestamp;
140            }
141
142            if (!mHasStarted) {
143                mHasStarted = true;
144                CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp,
145                        CameraDeviceState.NO_CAPTURE_ERROR);
146            }
147
148            tryComplete();
149        }
150
151        public void setJpegProduced() {
152            if (DEBUG) {
153                Log.d(TAG, "setJpegProduced - called for request " + mRequest.getRequestId());
154            }
155            if (!needsJpeg) {
156                throw new IllegalStateException(
157                        "setJpegProduced called for capture with no jpeg targets.");
158            }
159            if (isCompleted()) {
160                throw new IllegalStateException(
161                        "setJpegProduced called on already completed request.");
162            }
163
164            mReceivedFlags |= FLAG_RECEIVED_JPEG;
165            tryComplete();
166        }
167
168        public void setJpegFailed() {
169            if (DEBUG) {
170                Log.d(TAG, "setJpegFailed - called for request " + mRequest.getRequestId());
171            }
172            if (!needsJpeg || isJpegCompleted()) {
173                return;
174            }
175            mFailedJpeg = true;
176
177            mReceivedFlags |= FLAG_RECEIVED_JPEG;
178            mReceivedFlags |= FLAG_RECEIVED_JPEG_TS;
179            tryComplete();
180        }
181
182        public void setPreviewTimestamp(long timestamp) {
183            if (DEBUG) {
184                Log.d(TAG, "setPreviewTimestamp - called for request " + mRequest.getRequestId());
185            }
186            if (!needsPreview) {
187                throw new IllegalStateException(
188                        "setPreviewTimestamp called for capture with no preview targets.");
189            }
190            if (isCompleted()) {
191                throw new IllegalStateException(
192                        "setPreviewTimestamp called on already completed request.");
193            }
194
195            mReceivedFlags |= FLAG_RECEIVED_PREVIEW_TS;
196
197            if (mTimestamp == 0) {
198                mTimestamp = timestamp;
199            }
200
201            if (!needsJpeg) {
202                if (!mHasStarted) {
203                    mHasStarted = true;
204                    CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp,
205                            CameraDeviceState.NO_CAPTURE_ERROR);
206                }
207            }
208
209            tryComplete();
210        }
211
212        public void setPreviewProduced() {
213            if (DEBUG) {
214                Log.d(TAG, "setPreviewProduced - called for request " + mRequest.getRequestId());
215            }
216            if (!needsPreview) {
217                throw new IllegalStateException(
218                        "setPreviewProduced called for capture with no preview targets.");
219            }
220            if (isCompleted()) {
221                throw new IllegalStateException(
222                        "setPreviewProduced called on already completed request.");
223            }
224
225            mReceivedFlags |= FLAG_RECEIVED_PREVIEW;
226            tryComplete();
227        }
228
229        public void setPreviewFailed() {
230            if (DEBUG) {
231                Log.d(TAG, "setPreviewFailed - called for request " + mRequest.getRequestId());
232            }
233            if (!needsPreview || isPreviewCompleted()) {
234                return;
235            }
236            mFailedPreview = true;
237
238            mReceivedFlags |= FLAG_RECEIVED_PREVIEW;
239            mReceivedFlags |= FLAG_RECEIVED_PREVIEW_TS;
240            tryComplete();
241        }
242
243        // Comparison and equals based on frame number.
244        @Override
245        public int compareTo(CaptureHolder captureHolder) {
246            return (mRequest.getFrameNumber() > captureHolder.mRequest.getFrameNumber()) ? 1 :
247                    ((mRequest.getFrameNumber() == captureHolder.mRequest.getFrameNumber()) ? 0 :
248                            -1);
249        }
250
251        // Comparison and equals based on frame number.
252        @Override
253        public boolean equals(Object o) {
254            return o instanceof CaptureHolder && compareTo((CaptureHolder) o) == 0;
255        }
256    }
257
258    private final TreeSet<CaptureHolder> mActiveRequests;
259    private final ArrayDeque<CaptureHolder> mJpegCaptureQueue;
260    private final ArrayDeque<CaptureHolder> mJpegProduceQueue;
261    private final ArrayDeque<CaptureHolder> mPreviewCaptureQueue;
262    private final ArrayDeque<CaptureHolder> mPreviewProduceQueue;
263    private final ArrayList<CaptureHolder> mCompletedRequests = new ArrayList<>();
264
265    private final ReentrantLock mLock = new ReentrantLock();
266    private final Condition mIsEmpty;
267    private final Condition mPreviewsEmpty;
268    private final Condition mNotFull;
269    private final CameraDeviceState mDeviceState;
270    private int mInFlight = 0;
271    private int mInFlightPreviews = 0;
272    private final int mMaxInFlight;
273
274    /**
275     * Create a new {@link CaptureCollector} that can modify the given {@link CameraDeviceState}.
276     *
277     * @param maxInFlight max allowed in-flight requests.
278     * @param deviceState the {@link CameraDeviceState} to update as requests are processed.
279     */
280    public CaptureCollector(int maxInFlight, CameraDeviceState deviceState) {
281        mMaxInFlight = maxInFlight;
282        mJpegCaptureQueue = new ArrayDeque<>(MAX_JPEGS_IN_FLIGHT);
283        mJpegProduceQueue = new ArrayDeque<>(MAX_JPEGS_IN_FLIGHT);
284        mPreviewCaptureQueue = new ArrayDeque<>(mMaxInFlight);
285        mPreviewProduceQueue = new ArrayDeque<>(mMaxInFlight);
286        mActiveRequests = new TreeSet<>();
287        mIsEmpty = mLock.newCondition();
288        mNotFull = mLock.newCondition();
289        mPreviewsEmpty = mLock.newCondition();
290        mDeviceState = deviceState;
291    }
292
293    /**
294     * Queue a new request.
295     *
296     * <p>
297     * For requests that use the Camera1 API preview output stream, this will block if there are
298     * already {@code maxInFlight} requests in progress (until at least one prior request has
299     * completed). For requests that use the Camera1 API jpeg callbacks, this will block until
300     * all prior requests have been completed to avoid stopping preview for
301     * {@link android.hardware.Camera#takePicture} before prior preview requests have been
302     * completed.
303     * </p>
304     * @param holder the {@link RequestHolder} for this request.
305     * @param legacy the {@link LegacyRequest} for this request; this will not be mutated.
306     * @param timeout a timeout to use for this call.
307     * @param unit the units to use for the timeout.
308     * @return {@code false} if this method timed out.
309     * @throws InterruptedException if this thread is interrupted.
310     */
311    public boolean queueRequest(RequestHolder holder, LegacyRequest legacy, long timeout,
312                                TimeUnit unit)
313            throws InterruptedException {
314        CaptureHolder h = new CaptureHolder(holder, legacy);
315        long nanos = unit.toNanos(timeout);
316        final ReentrantLock lock = this.mLock;
317        lock.lock();
318        try {
319            if (DEBUG) {
320                Log.d(TAG, "queueRequest  for request " + holder.getRequestId() +
321                        " - " + mInFlight + " requests remain in flight.");
322            }
323
324            if (!(h.needsJpeg || h.needsPreview)) {
325                throw new IllegalStateException("Request must target at least one output surface!");
326            }
327
328            if (h.needsJpeg) {
329                // Wait for all current requests to finish before queueing jpeg.
330                while (mInFlight > 0) {
331                    if (nanos <= 0) {
332                        return false;
333                    }
334                    nanos = mIsEmpty.awaitNanos(nanos);
335                }
336                mJpegCaptureQueue.add(h);
337                mJpegProduceQueue.add(h);
338            }
339            if (h.needsPreview) {
340                while (mInFlight >= mMaxInFlight) {
341                    if (nanos <= 0) {
342                        return false;
343                    }
344                    nanos = mNotFull.awaitNanos(nanos);
345                }
346                mPreviewCaptureQueue.add(h);
347                mPreviewProduceQueue.add(h);
348                mInFlightPreviews++;
349            }
350            mActiveRequests.add(h);
351
352            mInFlight++;
353            return true;
354        } finally {
355            lock.unlock();
356        }
357    }
358
359    /**
360     * Wait all queued requests to complete.
361     *
362     * @param timeout a timeout to use for this call.
363     * @param unit the units to use for the timeout.
364     * @return {@code false} if this method timed out.
365     * @throws InterruptedException if this thread is interrupted.
366     */
367    public boolean waitForEmpty(long timeout, TimeUnit unit) throws InterruptedException {
368        long nanos = unit.toNanos(timeout);
369        final ReentrantLock lock = this.mLock;
370        lock.lock();
371        try {
372            while (mInFlight > 0) {
373                if (nanos <= 0) {
374                    return false;
375                }
376                nanos = mIsEmpty.awaitNanos(nanos);
377            }
378            return true;
379        } finally {
380            lock.unlock();
381        }
382    }
383
384    /**
385     * Wait all queued requests that use the Camera1 API preview output to complete.
386     *
387     * @param timeout a timeout to use for this call.
388     * @param unit the units to use for the timeout.
389     * @return {@code false} if this method timed out.
390     * @throws InterruptedException if this thread is interrupted.
391     */
392    public boolean waitForPreviewsEmpty(long timeout, TimeUnit unit) throws InterruptedException {
393        long nanos = unit.toNanos(timeout);
394        final ReentrantLock lock = this.mLock;
395        lock.lock();
396        try {
397            while (mInFlightPreviews > 0) {
398                if (nanos <= 0) {
399                    return false;
400                }
401                nanos = mPreviewsEmpty.awaitNanos(nanos);
402            }
403            return true;
404        } finally {
405            lock.unlock();
406        }
407    }
408
409    /**
410     * Wait for the specified request to be completed (all buffers available).
411     *
412     * <p>May not wait for the same request more than once, since a successful wait
413     * will erase the history of that request.</p>
414     *
415     * @param holder the {@link RequestHolder} for this request.
416     * @param timeout a timeout to use for this call.
417     * @param unit the units to use for the timeout.
418     * @param timestamp the timestamp of the request will be written out to here, in ns
419     *
420     * @return {@code false} if this method timed out.
421     *
422     * @throws InterruptedException if this thread is interrupted.
423     */
424    public boolean waitForRequestCompleted(RequestHolder holder, long timeout, TimeUnit unit,
425            MutableLong timestamp)
426            throws InterruptedException {
427        long nanos = unit.toNanos(timeout);
428        final ReentrantLock lock = this.mLock;
429        lock.lock();
430        try {
431            while (!removeRequestIfCompleted(holder, /*out*/timestamp)) {
432                if (nanos <= 0) {
433                    return false;
434                }
435                nanos = mNotFull.awaitNanos(nanos);
436            }
437            return true;
438        } finally {
439            lock.unlock();
440        }
441    }
442
443    private boolean removeRequestIfCompleted(RequestHolder holder, MutableLong timestamp) {
444        int i = 0;
445        for (CaptureHolder h : mCompletedRequests) {
446            if (h.mRequest.equals(holder)) {
447                timestamp.value = h.mTimestamp;
448                mCompletedRequests.remove(i);
449                return true;
450            }
451            i++;
452        }
453
454        return false;
455    }
456
457    /**
458     * Called to alert the {@link CaptureCollector} that the jpeg capture has begun.
459     *
460     * @param timestamp the time of the jpeg capture.
461     * @return the {@link RequestHolder} for the request associated with this capture.
462     */
463    public RequestHolder jpegCaptured(long timestamp) {
464        final ReentrantLock lock = this.mLock;
465        lock.lock();
466        try {
467            CaptureHolder h = mJpegCaptureQueue.poll();
468            if (h == null) {
469                Log.w(TAG, "jpegCaptured called with no jpeg request on queue!");
470                return null;
471            }
472            h.setJpegTimestamp(timestamp);
473            return h.mRequest;
474        } finally {
475            lock.unlock();
476        }
477    }
478
479    /**
480     * Called to alert the {@link CaptureCollector} that the jpeg capture has completed.
481     *
482     * @return a pair containing the {@link RequestHolder} and the timestamp of the capture.
483     */
484    public Pair<RequestHolder, Long> jpegProduced() {
485        final ReentrantLock lock = this.mLock;
486        lock.lock();
487        try {
488            CaptureHolder h = mJpegProduceQueue.poll();
489            if (h == null) {
490                Log.w(TAG, "jpegProduced called with no jpeg request on queue!");
491                return null;
492            }
493            h.setJpegProduced();
494            return new Pair<>(h.mRequest, h.mTimestamp);
495        } finally {
496            lock.unlock();
497        }
498    }
499
500    /**
501     * Check if there are any pending capture requests that use the Camera1 API preview output.
502     *
503     * @return {@code true} if there are pending preview requests.
504     */
505    public boolean hasPendingPreviewCaptures() {
506        final ReentrantLock lock = this.mLock;
507        lock.lock();
508        try {
509            return !mPreviewCaptureQueue.isEmpty();
510        } finally {
511            lock.unlock();
512        }
513    }
514
515    /**
516     * Called to alert the {@link CaptureCollector} that the preview capture has begun.
517     *
518     * @param timestamp the time of the preview capture.
519     * @return a pair containing the {@link RequestHolder} and the timestamp of the capture.
520     */
521    public Pair<RequestHolder, Long> previewCaptured(long timestamp) {
522        final ReentrantLock lock = this.mLock;
523        lock.lock();
524        try {
525            CaptureHolder h = mPreviewCaptureQueue.poll();
526            if (h == null) {
527                if (DEBUG) {
528                    Log.d(TAG, "previewCaptured called with no preview request on queue!");
529                }
530                return null;
531            }
532            h.setPreviewTimestamp(timestamp);
533            return new Pair<>(h.mRequest, h.mTimestamp);
534        } finally {
535            lock.unlock();
536        }
537    }
538
539    /**
540     * Called to alert the {@link CaptureCollector} that the preview capture has completed.
541     *
542     * @return the {@link RequestHolder} for the request associated with this capture.
543     */
544    public RequestHolder previewProduced() {
545        final ReentrantLock lock = this.mLock;
546        lock.lock();
547        try {
548            CaptureHolder h = mPreviewProduceQueue.poll();
549            if (h == null) {
550                Log.w(TAG, "previewProduced called with no preview request on queue!");
551                return null;
552            }
553            h.setPreviewProduced();
554            return h.mRequest;
555        } finally {
556            lock.unlock();
557        }
558    }
559
560    /**
561     * Called to alert the {@link CaptureCollector} that the next pending preview capture has failed.
562     */
563    public void failNextPreview() {
564        final ReentrantLock lock = this.mLock;
565        lock.lock();
566        try {
567            CaptureHolder h1 = mPreviewCaptureQueue.peek();
568            CaptureHolder h2 = mPreviewProduceQueue.peek();
569
570            // Find the request with the lowest frame number.
571            CaptureHolder h = (h1 == null) ? h2 :
572                              ((h2 == null) ? h1 :
573                              ((h1.compareTo(h2) <= 0) ? h1 :
574                              h2));
575
576            if (h != null) {
577                mPreviewCaptureQueue.remove(h);
578                mPreviewProduceQueue.remove(h);
579                mActiveRequests.remove(h);
580                h.setPreviewFailed();
581            }
582        } finally {
583            lock.unlock();
584        }
585    }
586
587    /**
588     * Called to alert the {@link CaptureCollector} that the next pending jpeg capture has failed.
589     */
590    public void failNextJpeg() {
591        final ReentrantLock lock = this.mLock;
592        lock.lock();
593        try {
594            CaptureHolder h1 = mJpegCaptureQueue.peek();
595            CaptureHolder h2 = mJpegProduceQueue.peek();
596
597            // Find the request with the lowest frame number.
598            CaptureHolder h = (h1 == null) ? h2 :
599                              ((h2 == null) ? h1 :
600                              ((h1.compareTo(h2) <= 0) ? h1 :
601                              h2));
602
603            if (h != null) {
604                mJpegCaptureQueue.remove(h);
605                mJpegProduceQueue.remove(h);
606                mActiveRequests.remove(h);
607                h.setJpegFailed();
608            }
609        } finally {
610            lock.unlock();
611        }
612    }
613
614    /**
615     * Called to alert the {@link CaptureCollector} all pending captures have failed.
616     */
617    public void failAll() {
618        final ReentrantLock lock = this.mLock;
619        lock.lock();
620        try {
621            CaptureHolder h;
622            while ((h = mActiveRequests.pollFirst()) != null) {
623                h.setPreviewFailed();
624                h.setJpegFailed();
625            }
626            mPreviewCaptureQueue.clear();
627            mPreviewProduceQueue.clear();
628            mJpegCaptureQueue.clear();
629            mJpegProduceQueue.clear();
630        } finally {
631            lock.unlock();
632        }
633    }
634
635    private void onPreviewCompleted() {
636        mInFlightPreviews--;
637        if (mInFlightPreviews < 0) {
638            throw new IllegalStateException(
639                    "More preview captures completed than requests queued.");
640        }
641        if (mInFlightPreviews == 0) {
642            mPreviewsEmpty.signalAll();
643        }
644    }
645
646    private void onRequestCompleted(CaptureHolder capture) {
647        RequestHolder request = capture.mRequest;
648
649        mInFlight--;
650        if (DEBUG) {
651            Log.d(TAG, "Completed request " + request.getRequestId() +
652                    ", " + mInFlight + " requests remain in flight.");
653        }
654        if (mInFlight < 0) {
655            throw new IllegalStateException(
656                    "More captures completed than requests queued.");
657        }
658
659        mCompletedRequests.add(capture);
660        mActiveRequests.remove(capture);
661
662        mNotFull.signalAll();
663        if (mInFlight == 0) {
664            mIsEmpty.signalAll();
665        }
666    }
667}
668