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