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 
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 = 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 
CaptureHolder(RequestHolder request, LegacyRequest legacyHolder)63         public CaptureHolder(RequestHolder request, LegacyRequest legacyHolder) {
64             mRequest = request;
65             mLegacy = legacyHolder;
66             needsJpeg = request.hasJpegTargets();
67             needsPreview = request.hasPreviewTargets();
68         }
69 
isPreviewCompleted()70         public boolean isPreviewCompleted() {
71             return (mReceivedFlags & FLAG_RECEIVED_ALL_PREVIEW) == FLAG_RECEIVED_ALL_PREVIEW;
72         }
73 
isJpegCompleted()74         public  boolean isJpegCompleted() {
75             return (mReceivedFlags & FLAG_RECEIVED_ALL_JPEG) == FLAG_RECEIVED_ALL_JPEG;
76         }
77 
isCompleted()78         public boolean isCompleted() {
79             return (needsJpeg == isJpegCompleted()) && (needsPreview == isPreviewCompleted());
80         }
81 
tryComplete()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 
setJpegTimestamp(long timestamp)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 
setJpegProduced()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 
setJpegFailed()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 
setPreviewTimestamp(long timestamp)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 
setPreviewProduced()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 
setPreviewFailed()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
compareTo(CaptureHolder captureHolder)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
equals(Object o)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      */
CaptureCollector(int maxInFlight, CameraDeviceState deviceState)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      */
queueRequest(RequestHolder holder, LegacyRequest legacy, long timeout, TimeUnit unit)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      */
waitForEmpty(long timeout, TimeUnit unit)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      */
waitForPreviewsEmpty(long timeout, TimeUnit unit)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      */
waitForRequestCompleted(RequestHolder holder, long timeout, TimeUnit unit, MutableLong timestamp)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 
removeRequestIfCompleted(RequestHolder holder, MutableLong timestamp)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      */
jpegCaptured(long timestamp)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      */
jpegProduced()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      */
hasPendingPreviewCaptures()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      */
previewCaptured(long timestamp)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      */
previewProduced()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      */
failNextPreview()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      */
failNextJpeg()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      */
failAll()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 
onPreviewCompleted()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 
onRequestCompleted(CaptureHolder capture)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