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 17 package com.android.camera.one.v2; 18 19 import android.annotation.TargetApi; 20 import android.hardware.camera2.CameraCaptureSession; 21 import android.hardware.camera2.CaptureRequest; 22 import android.hardware.camera2.CaptureResult; 23 import android.hardware.camera2.CaptureResult.Key; 24 import android.hardware.camera2.TotalCaptureResult; 25 import android.media.Image; 26 import android.media.ImageReader; 27 import android.os.Build; 28 import android.os.Handler; 29 import android.os.SystemClock; 30 import android.util.Pair; 31 32 import com.android.camera.debug.Log; 33 import com.android.camera.debug.Log.Tag; 34 import com.android.camera.util.ConcurrentSharedRingBuffer; 35 import com.android.camera.util.ConcurrentSharedRingBuffer.PinStateListener; 36 import com.android.camera.util.ConcurrentSharedRingBuffer.Selector; 37 import com.android.camera.util.ConcurrentSharedRingBuffer.SwapTask; 38 import com.android.camera.util.Task; 39 40 import java.util.Collections; 41 import java.util.List; 42 import java.util.Map; 43 import java.util.Set; 44 import java.util.concurrent.ConcurrentHashMap; 45 import java.util.concurrent.Executor; 46 import java.util.concurrent.RejectedExecutionException; 47 import java.util.concurrent.atomic.AtomicInteger; 48 49 /** 50 * Implements {@link android.media.ImageReader.OnImageAvailableListener} and 51 * {@link android.hardware.camera2.CameraCaptureSession.CaptureListener} to 52 * store the results of capture requests (both {@link Image}s and 53 * {@link TotalCaptureResult}s in a ring-buffer from which they may be saved. 54 * <br> 55 * This also manages the lifecycle of {@link Image}s within the application as 56 * they are passed in from the lower-level camera2 API. 57 */ 58 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 59 public class ImageCaptureManager extends CameraCaptureSession.CaptureCallback implements 60 ImageReader.OnImageAvailableListener { 61 /** 62 * Callback to listen for changes to the ability to capture an existing 63 * image from the internal ring-buffer. 64 */ 65 public interface CaptureReadyListener { 66 /** 67 * Called whenever the ability to capture an existing image from the 68 * ring-buffer changes. Calls to {@link #tryCaptureExistingImage} are 69 * more likely to succeed or fail depending on the value passed in to 70 * this function. 71 * 72 * @param capturePossible true if capture is more-likely to be possible, 73 * false if capture is less-likely to be possible. 74 */ onReadyStateChange(boolean capturePossible)75 public void onReadyStateChange(boolean capturePossible); 76 } 77 78 /** 79 * Callback for listening to changes to individual metadata values. 80 */ 81 public static interface MetadataChangeListener { 82 /** 83 * This will be called whenever a metadata value changes. 84 * Implementations should not take too much time to execute since this 85 * will be called faster than the camera's frame rate. 86 * 87 * @param key the {@link CaptureResult} key this listener listens for. 88 * @param second the previous value, or null if no such value existed. 89 * The type will be that associated with the 90 * {@link android.hardware.camera2.CaptureResult.Key} this 91 * listener is bound to. 92 * @param newValue the new value. The type will be that associated with 93 * the {@link android.hardware.camera2.CaptureResult.Key} 94 * this listener is bound to. 95 * @param result the CaptureResult containing the new value 96 */ onImageMetadataChange(Key<?> key, Object second, Object newValue, CaptureResult result)97 public void onImageMetadataChange(Key<?> key, Object second, Object newValue, 98 CaptureResult result); 99 } 100 101 /** 102 * Callback for saving an image. 103 */ 104 public interface ImageCaptureListener { 105 /** 106 * Called with the {@link Image} and associated 107 * {@link TotalCaptureResult}. A typical implementation would save this 108 * to disk. 109 * <p> 110 * Note: Implementations must be thread-safe and must not close the 111 * image. 112 * </p> 113 */ onImageCaptured(Image image, TotalCaptureResult captureResult)114 public void onImageCaptured(Image image, TotalCaptureResult captureResult); 115 } 116 117 /** 118 * Callback for placing constraints on which images to capture. See 119 * {@link #tryCaptureExistingImage} and {@link #captureNextImage}. 120 */ 121 public static interface CapturedImageConstraint { 122 /** 123 * Implementations should return true if the provided 124 * TotalCaptureResults satisfies constraints necessary for the intended 125 * image capture. For example, a constraint may return false if 126 * {@captureResult} indicates that the lens was moving during image 127 * capture. 128 * 129 * @param captureResult The metadata associated with the image. 130 * @return true if this image satisfies the constraint and can be 131 * captured, false otherwise. 132 */ satisfiesConstraint(TotalCaptureResult captureResult)133 boolean satisfiesConstraint(TotalCaptureResult captureResult); 134 } 135 136 /** 137 * Holds an {@link Image} and {@link TotalCaptureResult} pair which may be 138 * added asynchronously. 139 */ 140 private class CapturedImage { 141 /** 142 * The Image and TotalCaptureResult may be received at different times 143 * (via the onImageAvailableListener and onCaptureProgressed callbacks, 144 * respectively). 145 */ 146 private Image mImage = null; 147 private TotalCaptureResult mMetadata = null; 148 149 /** 150 * Resets the object, closing and removing any existing image and 151 * metadata. 152 */ reset()153 public void reset() { 154 if (mImage != null) { 155 mImage.close(); 156 int numOpenImages = mNumOpenImages.decrementAndGet(); 157 if (DEBUG_PRINT_OPEN_IMAGE_COUNT) { 158 Log.v(TAG, "Closed an image. Number of open images = " + numOpenImages); 159 } 160 } 161 162 mImage = null; 163 164 mMetadata = null; 165 } 166 167 /** 168 * @return true if both the image and metadata are present, false 169 * otherwise. 170 */ isComplete()171 public boolean isComplete() { 172 return mImage != null && mMetadata != null; 173 } 174 175 /** 176 * Adds the image. Note that this can only be called once before a 177 * {@link #reset()} is necessary. 178 * 179 * @param image the {@Link Image} to add. 180 */ addImage(Image image)181 public void addImage(Image image) { 182 if (mImage != null) { 183 throw new IllegalArgumentException( 184 "Unable to add an Image when one already exists."); 185 } 186 mImage = image; 187 } 188 189 /** 190 * Retrieves the {@link Image} if it has been added, returns null if it 191 * is not available yet. 192 */ tryGetImage()193 public Image tryGetImage() { 194 return mImage; 195 } 196 197 /** 198 * Adds the metadata. Note that this can only be called once before a 199 * {@link #reset()} is necessary. 200 * 201 * @param metadata the {@Link TotalCaptureResult} to add. 202 */ addMetadata(TotalCaptureResult metadata)203 public void addMetadata(TotalCaptureResult metadata) { 204 if (mMetadata != null) { 205 throw new IllegalArgumentException( 206 "Unable to add a TotalCaptureResult when one already exists."); 207 } 208 mMetadata = metadata; 209 } 210 211 /** 212 * Retrieves the {@link TotalCaptureResult} if it has been added, 213 * returns null if it is not available yet. 214 */ tryGetMetadata()215 public TotalCaptureResult tryGetMetadata() { 216 return mMetadata; 217 } 218 219 /** 220 * Returs the timestamp of the image if present, -1 otherwise. 221 */ tryGetTimestamp()222 public long tryGetTimestamp() { 223 if (mImage != null) { 224 return mImage.getTimestamp(); 225 } 226 if (mMetadata != null) { 227 return mMetadata.get(TotalCaptureResult.SENSOR_TIMESTAMP); 228 } 229 return -1; 230 } 231 } 232 233 private static final Tag TAG = new Tag("ZSLImageListener"); 234 235 /** 236 * If true, the number of open images will be printed to LogCat every time 237 * an image is opened or closed. 238 */ 239 private static final boolean DEBUG_PRINT_OPEN_IMAGE_COUNT = false; 240 241 /** 242 * The maximum duration for an onImageAvailable() callback before debugging 243 * output is printed. This is a little under 1/30th of a second to enable 244 * detecting jank in the preview stream caused by {@link #onImageAvailable} 245 * taking too long to return. 246 */ 247 private static final long DEBUG_MAX_IMAGE_CALLBACK_DUR = 25; 248 249 /** 250 * If spacing between onCaptureCompleted() callbacks is lower than this 251 * value, camera operations at the Java level have stalled, and are now 252 * catching up. In milliseconds. 253 */ 254 private static final long DEBUG_INTERFRAME_STALL_WARNING = 5; 255 256 /** 257 * Last called to onCaptureCompleted() in SystemClock.uptimeMillis(). 258 */ 259 private long mDebugLastOnCaptureCompletedMillis = 0; 260 261 /** 262 * Number of frames in a row exceeding DEBUG_INTERFRAME_STALL_WARNING. 263 */ 264 private long mDebugStalledFrameCount = 0; 265 266 /** 267 * Stores the ring-buffer of captured images.<br> 268 * Note that this takes care of thread-safe reference counting of images to 269 * ensure that they are never leaked by the app. 270 */ 271 private final ConcurrentSharedRingBuffer<CapturedImage> mCapturedImageBuffer; 272 273 /** Track the number of open images for debugging purposes. */ 274 private final AtomicInteger mNumOpenImages = new AtomicInteger(0); 275 276 /** 277 * The handler used to invoke light-weight listeners: 278 * {@link CaptureReadyListener} and {@link MetadataChangeListener}. 279 */ 280 private final Handler mListenerHandler; 281 282 /** 283 * The executor used to invoke {@link ImageCaptureListener}. Note that this 284 * is different from mListenerHandler because a typical ImageCaptureListener 285 * will compress the image to jpeg, and we may wish to execute these tasks 286 * on multiple threads. 287 */ 288 private final Executor mImageCaptureListenerExecutor; 289 290 /** 291 * The set of constraints which must be satisfied for a newly acquired image 292 * to be captured and sent to {@link #mPendingImageCaptureCallback}. null if 293 * there is no pending capture request. 294 */ 295 private List<ImageCaptureManager.CapturedImageConstraint> mPendingImageCaptureConstraints; 296 297 /** 298 * The callback to be invoked upon successfully capturing a newly-acquired 299 * image which satisfies {@link #mPendingImageCaptureConstraints}. null if 300 * there is no pending capture request. 301 */ 302 private ImageCaptureManager.ImageCaptureListener mPendingImageCaptureCallback; 303 304 /** 305 * Map from CaptureResult key to the frame number of the capture result 306 * containing the most recent value for this key and the most recent value 307 * of the key. 308 */ 309 private final Map<Key<?>, Pair<Long, Object>> 310 mMetadata = new ConcurrentHashMap<CaptureResult.Key<?>, Pair<Long, Object>>(); 311 312 /** 313 * The set of callbacks to be invoked when an entry in {@link #mMetadata} is 314 * changed. 315 */ 316 private final Map<Key<?>, Set<MetadataChangeListener>> 317 mMetadataChangeListeners = new ConcurrentHashMap<Key<?>, Set<MetadataChangeListener>>(); 318 319 /** 320 * @param maxImages the maximum number of images provided by the 321 * {@link ImageReader}. This must be greater than 2. 322 * @param listenerHandler the handler on which to invoke listeners. Note 323 * that this should probably be on a different thread than the 324 * one used for camera operations, such as capture requests and 325 * OnImageAvailable listeners, to avoid stalling the preview. 326 * @param imageCaptureListenerExecutor the executor on which to invoke image 327 * capture listeners, {@link ImageCaptureListener}. 328 */ ImageCaptureManager(int maxImages, Handler listenerHandler, Executor imageCaptureListenerExecutor)329 ImageCaptureManager(int maxImages, Handler listenerHandler, 330 Executor imageCaptureListenerExecutor) { 331 // Ensure that there are always 2 images available for the framework to 332 // continue processing frames. 333 // TODO Could we make this tighter? 334 mCapturedImageBuffer = new ConcurrentSharedRingBuffer<ImageCaptureManager.CapturedImage>( 335 maxImages - 2); 336 337 mListenerHandler = listenerHandler; 338 mImageCaptureListenerExecutor = imageCaptureListenerExecutor; 339 } 340 341 /** 342 * See {@link CaptureReadyListener}. 343 */ setCaptureReadyListener(final CaptureReadyListener listener)344 public void setCaptureReadyListener(final CaptureReadyListener listener) { 345 mCapturedImageBuffer.setListener(mListenerHandler, 346 new PinStateListener() { 347 @Override 348 public void onPinStateChange(boolean pinsAvailable) { 349 listener.onReadyStateChange(pinsAvailable); 350 } 351 }); 352 } 353 354 /** 355 * Adds a metadata stream listener associated with the given key. 356 * 357 * @param key the key of the metadata to track. 358 * @param listener the listener to be invoked when the value associated with 359 * key changes. 360 */ addMetadataChangeListener(Key<T> key, MetadataChangeListener listener)361 public <T> void addMetadataChangeListener(Key<T> key, MetadataChangeListener listener) { 362 if (!mMetadataChangeListeners.containsKey(key)) { 363 // Listeners may be added to this set from a different thread than 364 // that which must iterate over this set to invoke the listeners. 365 // Therefore, we need a thread save hash set. 366 mMetadataChangeListeners.put(key, 367 Collections.newSetFromMap(new ConcurrentHashMap< 368 ImageCaptureManager.MetadataChangeListener, Boolean>())); 369 } 370 mMetadataChangeListeners.get(key).add(listener); 371 } 372 373 /** 374 * Removes the metadata stream listener associated with the given key. 375 * 376 * @param key the key associated with the metadata to track. 377 * @param listener the listener to be invoked when the value associated with 378 * key changes. 379 * @return true if the listener was removed, false if no such listener had 380 * been added. 381 */ removeMetadataChangeListener(Key<T> key, MetadataChangeListener listener)382 public <T> boolean removeMetadataChangeListener(Key<T> key, MetadataChangeListener listener) { 383 if (!mMetadataChangeListeners.containsKey(key)) { 384 return false; 385 } else { 386 return mMetadataChangeListeners.get(key).remove(listener); 387 } 388 } 389 390 @Override onCaptureProgressed(CameraCaptureSession session, CaptureRequest request, final CaptureResult partialResult)391 public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request, 392 final CaptureResult partialResult) { 393 updateMetadataChangeListeners(partialResult); 394 } 395 396 @Override onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, final TotalCaptureResult result)397 public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, 398 final TotalCaptureResult result) { 399 final long timestamp = result.get(TotalCaptureResult.SENSOR_TIMESTAMP); 400 401 updateMetadataChangeListeners(result); 402 403 // Detect camera thread stall. 404 long now = SystemClock.uptimeMillis(); 405 if (now - mDebugLastOnCaptureCompletedMillis < DEBUG_INTERFRAME_STALL_WARNING) { 406 Log.e(TAG, "Camera thread has stalled for " + ++mDebugStalledFrameCount + 407 " frames at # " + result.getFrameNumber() + "."); 408 } else { 409 mDebugStalledFrameCount = 0; 410 } 411 mDebugLastOnCaptureCompletedMillis = now; 412 413 // Find the CapturedImage in the ring-buffer and attach the 414 // TotalCaptureResult to it. 415 // See documentation for swapLeast() for details. 416 boolean swapSuccess = doMetaDataSwap(result, timestamp); 417 if (!swapSuccess) { 418 // Do nothing on failure to swap in. 419 Log.v(TAG, "Unable to add new image metadata to ring-buffer."); 420 } 421 422 tryExecutePendingCaptureRequest(timestamp); 423 } 424 updateMetadataChangeListeners(final CaptureResult result)425 private void updateMetadataChangeListeners(final CaptureResult result) { 426 long frameNumber = result.getFrameNumber(); 427 428 // Update mMetadata for whichever keys are present, if this frame is 429 // supplying newer values. 430 for (final Key<?> key : result.getKeys()) { 431 Pair<Long, Object> oldEntry = mMetadata.get(key); 432 final Object oldValue = (oldEntry != null) ? oldEntry.second : null; 433 434 boolean newerValueAlreadyExists = oldEntry != null 435 && frameNumber < oldEntry.first; 436 if (newerValueAlreadyExists) { 437 continue; 438 } 439 440 final Object newValue = result.get(key); 441 mMetadata.put(key, new Pair<Long, Object>(frameNumber, newValue)); 442 443 // If the value has changed, call the appropriate listeners, if 444 // any exist. 445 if (oldValue == newValue || !mMetadataChangeListeners.containsKey(key)) { 446 continue; 447 } 448 449 for (final MetadataChangeListener listener : 450 mMetadataChangeListeners.get(key)) { 451 mListenerHandler.post(new Runnable() { 452 @Override 453 public void run() { 454 listener.onImageMetadataChange(key, oldValue, newValue, 455 result); 456 } 457 }); 458 } 459 } 460 } 461 462 private boolean doMetaDataSwap(final TotalCaptureResult newMetadata, final long timestamp) { 463 return mCapturedImageBuffer.swapLeast(timestamp, 464 new SwapTask<CapturedImage>() { 465 @Override 466 public CapturedImage create() { 467 CapturedImage image = new CapturedImage(); 468 image.addMetadata(newMetadata); 469 return image; 470 } 471 472 @Override 473 public CapturedImage swap(CapturedImage oldElement) { 474 oldElement.reset(); 475 oldElement.addMetadata(newMetadata); 476 return oldElement; 477 } 478 479 @Override 480 public void update(CapturedImage existingElement) { 481 existingElement.addMetadata(newMetadata); 482 } 483 484 @Override 485 public long getSwapKey() { 486 return -1; 487 } 488 }); 489 } 490 491 private boolean doImageSwap(final Image newImage) { 492 return mCapturedImageBuffer.swapLeast(newImage.getTimestamp(), 493 new SwapTask<CapturedImage>() { 494 @Override 495 public CapturedImage create() { 496 CapturedImage image = new CapturedImage(); 497 image.addImage(newImage); 498 return image; 499 } 500 501 @Override 502 public CapturedImage swap(CapturedImage oldElement) { 503 oldElement.reset(); 504 CapturedImage image = new CapturedImage(); 505 image.addImage(newImage); 506 return image; 507 } 508 509 @Override 510 public void update(CapturedImage existingElement) { 511 existingElement.addImage(newImage); 512 } 513 514 @Override 515 public long getSwapKey() { 516 return -1; 517 } 518 }); 519 } 520 521 @Override 522 public void onImageAvailable(ImageReader reader) { 523 long startTime = SystemClock.currentThreadTimeMillis(); 524 525 final Image img = reader.acquireLatestImage(); 526 527 if (img != null) { 528 int numOpenImages = mNumOpenImages.incrementAndGet(); 529 if (DEBUG_PRINT_OPEN_IMAGE_COUNT) { 530 Log.v(TAG, "Acquired an image. Number of open images = " + numOpenImages); 531 } 532 533 long timestamp = img.getTimestamp(); 534 // Try to place the newly-acquired image into the ring buffer. 535 boolean swapSuccess = doImageSwap(img); 536 if (!swapSuccess) { 537 // If we were unable to save the image to the ring buffer, we 538 // must close it now. 539 // We should only get here if the ring buffer is closed. 540 img.close(); 541 numOpenImages = mNumOpenImages.decrementAndGet(); 542 if (DEBUG_PRINT_OPEN_IMAGE_COUNT) { 543 Log.v(TAG, "Closed an image. Number of open images = " + numOpenImages); 544 } 545 } 546 547 tryExecutePendingCaptureRequest(timestamp); 548 549 long endTime = SystemClock.currentThreadTimeMillis(); 550 long totTime = endTime - startTime; 551 if (totTime > DEBUG_MAX_IMAGE_CALLBACK_DUR) { 552 // If it takes too long to swap elements, we will start skipping 553 // preview frames, resulting in visible jank. 554 Log.v(TAG, "onImageAvailable() took " + totTime + "ms"); 555 } 556 } 557 } 558 559 /** 560 * Closes the listener, eventually freeing all currently-held {@link Image} 561 * s. 562 */ 563 public void close() { 564 closeBuffer(); 565 } 566 567 /** 568 * Sets the pending image capture request, overriding any previous calls to 569 * {@link #captureNextImage} which have not yet been resolved. When the next 570 * available image which satisfies the given constraints can be captured, 571 * onImageCaptured will be invoked. 572 * 573 * @param onImageCaptured the callback which will be invoked with the 574 * captured image. 575 * @param constraints the set of constraints which must be satisfied in 576 * order for the image to be captured. 577 */ 578 public void captureNextImage(final ImageCaptureListener onImageCaptured, 579 final List<CapturedImageConstraint> constraints) { 580 mPendingImageCaptureCallback = onImageCaptured; 581 mPendingImageCaptureConstraints = constraints; 582 } 583 584 /** 585 * Tries to resolve any pending image capture requests. 586 * 587 * @param newImageTimestamp the timestamp of a newly-acquired image which 588 * should be captured if appropriate and possible. 589 */ 590 private void tryExecutePendingCaptureRequest(long newImageTimestamp) { 591 if (mPendingImageCaptureCallback != null) { 592 final Pair<Long, CapturedImage> pinnedImage = mCapturedImageBuffer.tryPin( 593 newImageTimestamp); 594 if (pinnedImage != null) { 595 CapturedImage image = pinnedImage.second; 596 597 if (!image.isComplete()) { 598 mCapturedImageBuffer.release(pinnedImage.first); 599 return; 600 } 601 602 // Check to see if the image satisfies all constraints. 603 TotalCaptureResult captureResult = image.tryGetMetadata(); 604 605 if (mPendingImageCaptureConstraints != null) { 606 for (CapturedImageConstraint constraint : mPendingImageCaptureConstraints) { 607 if (!constraint.satisfiesConstraint(captureResult)) { 608 mCapturedImageBuffer.release(pinnedImage.first); 609 return; 610 } 611 } 612 } 613 614 // If we get here, the image satisfies all the necessary 615 // constraints. 616 617 if (tryExecuteCaptureOrRelease(pinnedImage, mPendingImageCaptureCallback)) { 618 // If we successfully handed the image off to the callback, 619 // remove the pending 620 // capture request. 621 mPendingImageCaptureCallback = null; 622 mPendingImageCaptureConstraints = null; 623 } 624 } 625 } 626 } 627 628 /** 629 * Tries to capture an existing image from the ring-buffer, if one exists 630 * that satisfies the given constraint and can be pinned. 631 * 632 * @return true if the image could be captured, false otherwise. 633 */ 634 public boolean tryCaptureExistingImage(final ImageCaptureListener onImageCaptured, 635 final List<CapturedImageConstraint> constraints) { 636 // The selector to use in choosing the image to capture. 637 Selector<ImageCaptureManager.CapturedImage> selector; 638 639 if (constraints == null || constraints.isEmpty()) { 640 // If there are no constraints, use a trivial Selector. 641 selector = new Selector<ImageCaptureManager.CapturedImage>() { 642 @Override 643 public boolean select(CapturedImage image) { 644 return true; 645 } 646 }; 647 } else { 648 // If there are constraints, create a Selector which will return 649 // true if all constraints 650 // are satisfied. 651 selector = new Selector<ImageCaptureManager.CapturedImage>() { 652 @Override 653 public boolean select(CapturedImage e) { 654 // If this image already has metadata associated with it, 655 // then use it. 656 // Otherwise, we can't block until it's available, so assume 657 // it doesn't 658 // satisfy the required constraints. 659 TotalCaptureResult captureResult = e.tryGetMetadata(); 660 661 if (captureResult == null || e.tryGetImage() == null) { 662 return false; 663 } 664 665 for (CapturedImageConstraint constraint : constraints) { 666 if (!constraint.satisfiesConstraint(captureResult)) { 667 return false; 668 } 669 } 670 return true; 671 } 672 }; 673 } 674 675 // Acquire a lock (pin) on the most recent (greatest-timestamp) image in 676 // the ring buffer which satisfies our constraints. 677 // Note that this must be released as soon as we are done with it. 678 final Pair<Long, CapturedImage> toCapture = mCapturedImageBuffer.tryPinGreatestSelected( 679 selector); 680 681 return tryExecuteCaptureOrRelease(toCapture, onImageCaptured); 682 } 683 684 /** 685 * Tries to execute the image capture callback with the pinned CapturedImage 686 * provided. 687 * 688 * @param toCapture The pinned CapturedImage to pass to the callback, or 689 * release on failure. 690 * @param callback The callback to execute. 691 * @return true upon success, false upon failure and the release of the 692 * pinned image. 693 */ 694 private boolean tryExecuteCaptureOrRelease(final Pair<Long, CapturedImage> toCapture, 695 final ImageCaptureListener callback) { 696 if (toCapture == null) { 697 return false; 698 } else { 699 try { 700 mImageCaptureListenerExecutor.execute(new Runnable() { 701 @Override 702 public void run() { 703 try { 704 CapturedImage img = toCapture.second; 705 callback.onImageCaptured(img.tryGetImage(), 706 img.tryGetMetadata()); 707 } finally { 708 mCapturedImageBuffer.release(toCapture.first); 709 } 710 } 711 }); 712 } catch (RejectedExecutionException e) { 713 // We may get here if the thread pool has been closed. 714 mCapturedImageBuffer.release(toCapture.first); 715 return false; 716 } 717 718 return true; 719 } 720 } 721 722 /** 723 * Tries to capture a pinned image for the given key from the ring-buffer. 724 * 725 * @return the pair of (image, captureResult) if image is found, null 726 * otherwise. 727 */ 728 public Pair<Image, TotalCaptureResult> 729 tryCapturePinnedImage(long timestamp) { 730 final Pair<Long, CapturedImage> toCapture = 731 mCapturedImageBuffer.tryGetPinned(timestamp); 732 Image pinnedImage = null; 733 TotalCaptureResult imageCaptureResult = null; 734 // Return an Image 735 if (toCapture != null && toCapture.second != null) { 736 pinnedImage = toCapture.second.tryGetImage(); 737 imageCaptureResult = toCapture.second.tryGetMetadata(); 738 } 739 return Pair.create(pinnedImage, imageCaptureResult); 740 } 741 742 /** 743 * Clear the buffer and reserves <code>unpinnedReservedSlots</code> in the buffer. 744 * 745 * @param unpinnedReservedSlots the number of unpinned slots that are never 746 * allowed to be pinned. 747 */ 748 private void clearCapturedImageBuffer(int unpinnedReservedSlots) { 749 mCapturedImageBuffer.releaseAll(); 750 closeBuffer(); 751 try { 752 mCapturedImageBuffer.reopenBuffer(unpinnedReservedSlots); 753 } catch (InterruptedException e) { 754 e.printStackTrace(); 755 } 756 } 757 758 /** 759 * Closes the buffer and frees up any images in the buffer. 760 */ 761 private void closeBuffer() { 762 try { 763 mCapturedImageBuffer.close(new Task<CapturedImage>() { 764 @Override 765 public void run(CapturedImage e) { 766 e.reset(); 767 } 768 }); 769 } catch (InterruptedException e) { 770 e.printStackTrace(); 771 } 772 } 773 } 774