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