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 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 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 setJpegTimestamp(long timestamp)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 setJpegProduced()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 setJpegFailed()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 setPreviewTimestamp(long timestamp)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 setPreviewProduced()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 setPreviewFailed()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 compareTo(CaptureHolder captureHolder)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 equals(Object o)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 */ CaptureCollector(int maxInFlight, CameraDeviceState deviceState)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 */ queueRequest(RequestHolder holder, LegacyRequest legacy, long timeout, TimeUnit unit)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 */ waitForEmpty(long timeout, TimeUnit unit)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 */ waitForPreviewsEmpty(long timeout, TimeUnit unit)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 */ waitForRequestCompleted(RequestHolder holder, long timeout, TimeUnit unit, MutableLong timestamp)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 removeRequestIfCompleted(RequestHolder holder, MutableLong timestamp)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 */ jpegCaptured(long timestamp)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 */ jpegProduced()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 */ hasPendingPreviewCaptures()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 */ previewCaptured(long timestamp)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 */ previewProduced()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 */ failNextPreview()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 */ failNextJpeg()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 */ failAll()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 onPreviewCompleted()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 onRequestCompleted(CaptureHolder capture)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