CaptureCollector.java revision 971251f362410caebe8eeb5b6c13d8912f72d50e
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 */ 16package android.hardware.camera2.legacy; 17 18import android.hardware.camera2.impl.CameraMetadataNative; 19import android.util.Log; 20import android.util.Pair; 21 22import java.util.ArrayDeque; 23import java.util.concurrent.TimeUnit; 24import java.util.concurrent.locks.Condition; 25import java.util.concurrent.locks.ReentrantLock; 26 27/** 28 * Collect timestamps and state for each {@link CaptureRequest} as it passes through 29 * the Legacy camera pipeline. 30 */ 31public class CaptureCollector { 32 private static final String TAG = "CaptureCollector"; 33 34 private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG); 35 36 private static final int FLAG_RECEIVED_JPEG = 1; 37 private static final int FLAG_RECEIVED_JPEG_TS = 2; 38 private static final int FLAG_RECEIVED_PREVIEW = 4; 39 private static final int FLAG_RECEIVED_PREVIEW_TS = 8; 40 private static final int FLAG_RECEIVED_ALL_JPEG = FLAG_RECEIVED_JPEG | FLAG_RECEIVED_JPEG_TS; 41 private static final int FLAG_RECEIVED_ALL_PREVIEW = FLAG_RECEIVED_PREVIEW | 42 FLAG_RECEIVED_PREVIEW_TS; 43 44 private static final int MAX_JPEGS_IN_FLIGHT = 1; 45 46 private class CaptureHolder { 47 private final RequestHolder mRequest; 48 private final LegacyRequest mLegacy; 49 public final boolean needsJpeg; 50 public final boolean needsPreview; 51 52 private long mTimestamp = 0; 53 private int mReceivedFlags = 0; 54 private boolean mHasStarted = false; 55 56 public CaptureHolder(RequestHolder request, LegacyRequest legacyHolder) { 57 mRequest = request; 58 mLegacy = legacyHolder; 59 needsJpeg = request.hasJpegTargets(); 60 needsPreview = request.hasPreviewTargets(); 61 } 62 63 public boolean isPreviewCompleted() { 64 return (mReceivedFlags & FLAG_RECEIVED_ALL_PREVIEW) == FLAG_RECEIVED_ALL_PREVIEW; 65 } 66 67 public boolean isJpegCompleted() { 68 return (mReceivedFlags & FLAG_RECEIVED_ALL_JPEG) == FLAG_RECEIVED_ALL_JPEG; 69 } 70 71 public boolean isCompleted() { 72 return (needsJpeg == isJpegCompleted()) && (needsPreview == isPreviewCompleted()); 73 } 74 75 public void tryComplete() { 76 if (isCompleted()) { 77 if (needsPreview && isPreviewCompleted()) { 78 CaptureCollector.this.onPreviewCompleted(); 79 } 80 CaptureCollector.this.onRequestCompleted(mRequest, mLegacy, mTimestamp); 81 } 82 } 83 84 public void setJpegTimestamp(long timestamp) { 85 if (DEBUG) { 86 Log.d(TAG, "setJpegTimestamp - called for request " + mRequest.getRequestId()); 87 } 88 if (!needsJpeg) { 89 throw new IllegalStateException( 90 "setJpegTimestamp called for capture with no jpeg targets."); 91 } 92 if (isCompleted()) { 93 throw new IllegalStateException( 94 "setJpegTimestamp called on already completed request."); 95 } 96 97 mReceivedFlags |= FLAG_RECEIVED_JPEG_TS; 98 99 if (mTimestamp == 0) { 100 mTimestamp = timestamp; 101 } 102 103 if (!mHasStarted) { 104 mHasStarted = true; 105 CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp); 106 } 107 108 tryComplete(); 109 } 110 111 public void setJpegProduced() { 112 if (DEBUG) { 113 Log.d(TAG, "setJpegProduced - called for request " + mRequest.getRequestId()); 114 } 115 if (!needsJpeg) { 116 throw new IllegalStateException( 117 "setJpegProduced called for capture with no jpeg targets."); 118 } 119 if (isCompleted()) { 120 throw new IllegalStateException( 121 "setJpegProduced called on already completed request."); 122 } 123 124 mReceivedFlags |= FLAG_RECEIVED_JPEG; 125 tryComplete(); 126 } 127 128 public void setPreviewTimestamp(long timestamp) { 129 if (DEBUG) { 130 Log.d(TAG, "setPreviewTimestamp - called for request " + mRequest.getRequestId()); 131 } 132 if (!needsPreview) { 133 throw new IllegalStateException( 134 "setPreviewTimestamp called for capture with no preview targets."); 135 } 136 if (isCompleted()) { 137 throw new IllegalStateException( 138 "setPreviewTimestamp called on already completed request."); 139 } 140 141 mReceivedFlags |= FLAG_RECEIVED_PREVIEW_TS; 142 143 if (mTimestamp == 0) { 144 mTimestamp = timestamp; 145 } 146 147 if (!needsJpeg) { 148 if (!mHasStarted) { 149 mHasStarted = true; 150 CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp); 151 } 152 } 153 154 tryComplete(); 155 } 156 157 public void setPreviewProduced() { 158 if (DEBUG) { 159 Log.d(TAG, "setPreviewProduced - called for request " + mRequest.getRequestId()); 160 } 161 if (!needsPreview) { 162 throw new IllegalStateException( 163 "setPreviewProduced called for capture with no preview targets."); 164 } 165 if (isCompleted()) { 166 throw new IllegalStateException( 167 "setPreviewProduced called on already completed request."); 168 } 169 170 mReceivedFlags |= FLAG_RECEIVED_PREVIEW; 171 tryComplete(); 172 } 173 } 174 175 private final ArrayDeque<CaptureHolder> mJpegCaptureQueue; 176 private final ArrayDeque<CaptureHolder> mJpegProduceQueue; 177 private final ArrayDeque<CaptureHolder> mPreviewCaptureQueue; 178 private final ArrayDeque<CaptureHolder> mPreviewProduceQueue; 179 180 private final ReentrantLock mLock = new ReentrantLock(); 181 private final Condition mIsEmpty; 182 private final Condition mPreviewsEmpty; 183 private final Condition mNotFull; 184 private final CameraDeviceState mDeviceState; 185 private final LegacyResultMapper mMapper = new LegacyResultMapper(); 186 private int mInFlight = 0; 187 private int mInFlightPreviews = 0; 188 private final int mMaxInFlight; 189 190 /** 191 * Create a new {@link CaptureCollector} that can modify the given {@link CameraDeviceState}. 192 * 193 * @param maxInFlight max allowed in-flight requests. 194 * @param deviceState the {@link CameraDeviceState} to update as requests are processed. 195 */ 196 public CaptureCollector(int maxInFlight, CameraDeviceState deviceState) { 197 mMaxInFlight = maxInFlight; 198 mJpegCaptureQueue = new ArrayDeque<>(MAX_JPEGS_IN_FLIGHT); 199 mJpegProduceQueue = new ArrayDeque<>(MAX_JPEGS_IN_FLIGHT); 200 mPreviewCaptureQueue = new ArrayDeque<>(mMaxInFlight); 201 mPreviewProduceQueue = new ArrayDeque<>(mMaxInFlight); 202 mIsEmpty = mLock.newCondition(); 203 mNotFull = mLock.newCondition(); 204 mPreviewsEmpty = mLock.newCondition(); 205 mDeviceState = deviceState; 206 } 207 208 /** 209 * Queue a new request. 210 * 211 * <p> 212 * For requests that use the Camera1 API preview output stream, this will block if there are 213 * already {@code maxInFlight} requests in progress (until at least one prior request has 214 * completed). For requests that use the Camera1 API jpeg callbacks, this will block until 215 * all prior requests have been completed to avoid stopping preview for 216 * {@link android.hardware.Camera#takePicture} before prior preview requests have been 217 * completed. 218 * </p> 219 * @param holder the {@link RequestHolder} for this request. 220 * @param legacy the {@link LegacyRequest} for this request; this will not be mutated. 221 * @param timeout a timeout to use for this call. 222 * @param unit the units to use for the timeout. 223 * @return {@code false} if this method timed out. 224 * @throws InterruptedException if this thread is interrupted. 225 */ 226 public boolean queueRequest(RequestHolder holder, LegacyRequest legacy, long timeout, 227 TimeUnit unit) 228 throws InterruptedException { 229 CaptureHolder h = new CaptureHolder(holder, legacy); 230 long nanos = unit.toNanos(timeout); 231 final ReentrantLock lock = this.mLock; 232 lock.lock(); 233 try { 234 if (DEBUG) { 235 Log.d(TAG, "queueRequest for request " + holder.getRequestId() + 236 " - " + mInFlight + " requests remain in flight."); 237 } 238 if (h.needsJpeg) { 239 // Wait for all current requests to finish before queueing jpeg. 240 while (mInFlight > 0) { 241 if (nanos <= 0) { 242 return false; 243 } 244 nanos = mIsEmpty.awaitNanos(nanos); 245 } 246 mJpegCaptureQueue.add(h); 247 mJpegProduceQueue.add(h); 248 } 249 if (h.needsPreview) { 250 while (mInFlight >= mMaxInFlight) { 251 if (nanos <= 0) { 252 return false; 253 } 254 nanos = mNotFull.awaitNanos(nanos); 255 } 256 mPreviewCaptureQueue.add(h); 257 mPreviewProduceQueue.add(h); 258 mInFlightPreviews++; 259 } 260 261 if (!(h.needsJpeg || h.needsPreview)) { 262 throw new IllegalStateException("Request must target at least one output surface!"); 263 } 264 265 mInFlight++; 266 return true; 267 } finally { 268 lock.unlock(); 269 } 270 } 271 272 /** 273 * Wait all queued requests to complete. 274 * 275 * @param timeout a timeout to use for this call. 276 * @param unit the units to use for the timeout. 277 * @return {@code false} if this method timed out. 278 * @throws InterruptedException if this thread is interrupted. 279 */ 280 public boolean waitForEmpty(long timeout, TimeUnit unit) throws InterruptedException { 281 long nanos = unit.toNanos(timeout); 282 final ReentrantLock lock = this.mLock; 283 lock.lock(); 284 try { 285 while (mInFlight > 0) { 286 if (nanos <= 0) { 287 return false; 288 } 289 nanos = mIsEmpty.awaitNanos(nanos); 290 } 291 return true; 292 } finally { 293 lock.unlock(); 294 } 295 } 296 297 /** 298 * Wait all queued requests that use the Camera1 API preview output to complete. 299 * 300 * @param timeout a timeout to use for this call. 301 * @param unit the units to use for the timeout. 302 * @return {@code false} if this method timed out. 303 * @throws InterruptedException if this thread is interrupted. 304 */ 305 public boolean waitForPreviewsEmpty(long timeout, TimeUnit unit) throws InterruptedException { 306 long nanos = unit.toNanos(timeout); 307 final ReentrantLock lock = this.mLock; 308 lock.lock(); 309 try { 310 while (mInFlightPreviews > 0) { 311 if (nanos <= 0) { 312 return false; 313 } 314 nanos = mPreviewsEmpty.awaitNanos(nanos); 315 } 316 return true; 317 } finally { 318 lock.unlock(); 319 } 320 } 321 322 /** 323 * Called to alert the {@link CaptureCollector} that the jpeg capture has begun. 324 * 325 * @param timestamp the time of the jpeg capture. 326 * @return the {@link RequestHolder} for the request associated with this capture. 327 */ 328 public RequestHolder jpegCaptured(long timestamp) { 329 final ReentrantLock lock = this.mLock; 330 lock.lock(); 331 try { 332 CaptureHolder h = mJpegCaptureQueue.poll(); 333 if (h == null) { 334 Log.w(TAG, "jpegCaptured called with no jpeg request on queue!"); 335 return null; 336 } 337 h.setJpegTimestamp(timestamp); 338 return h.mRequest; 339 } finally { 340 lock.unlock(); 341 } 342 } 343 344 /** 345 * Called to alert the {@link CaptureCollector} that the jpeg capture has completed. 346 * 347 * @return a pair containing the {@link RequestHolder} and the timestamp of the capture. 348 */ 349 public Pair<RequestHolder, Long> jpegProduced() { 350 final ReentrantLock lock = this.mLock; 351 lock.lock(); 352 try { 353 CaptureHolder h = mJpegProduceQueue.poll(); 354 if (h == null) { 355 Log.w(TAG, "jpegProduced called with no jpeg request on queue!"); 356 return null; 357 } 358 h.setJpegProduced(); 359 return new Pair<>(h.mRequest, h.mTimestamp); 360 } finally { 361 lock.unlock(); 362 } 363 } 364 365 /** 366 * Check if there are any pending capture requests that use the Camera1 API preview output. 367 * 368 * @return {@code true} if there are pending preview requests. 369 */ 370 public boolean hasPendingPreviewCaptures() { 371 final ReentrantLock lock = this.mLock; 372 lock.lock(); 373 try { 374 return !mPreviewCaptureQueue.isEmpty(); 375 } finally { 376 lock.unlock(); 377 } 378 } 379 380 /** 381 * Called to alert the {@link CaptureCollector} that the preview capture has begun. 382 * 383 * @param timestamp the time of the preview capture. 384 * @return a pair containing the {@link RequestHolder} and the timestamp of the capture. 385 */ 386 public Pair<RequestHolder, Long> previewCaptured(long timestamp) { 387 final ReentrantLock lock = this.mLock; 388 lock.lock(); 389 try { 390 CaptureHolder h = mPreviewCaptureQueue.poll(); 391 if (h == null) { 392 Log.w(TAG, "previewCaptured called with no preview request on queue!"); 393 return null; 394 } 395 h.setPreviewTimestamp(timestamp); 396 return new Pair<>(h.mRequest, h.mTimestamp); 397 } finally { 398 lock.unlock(); 399 } 400 } 401 402 /** 403 * Called to alert the {@link CaptureCollector} that the preview capture has completed. 404 * 405 * @return the {@link RequestHolder} for the request associated with this capture. 406 */ 407 public RequestHolder previewProduced() { 408 final ReentrantLock lock = this.mLock; 409 lock.lock(); 410 try { 411 CaptureHolder h = mPreviewProduceQueue.poll(); 412 if (h == null) { 413 Log.w(TAG, "previewProduced called with no preview request on queue!"); 414 return null; 415 } 416 h.setPreviewProduced(); 417 return h.mRequest; 418 } finally { 419 lock.unlock(); 420 } 421 } 422 423 private void onPreviewCompleted() { 424 mInFlightPreviews--; 425 if (mInFlightPreviews < 0) { 426 throw new IllegalStateException( 427 "More preview captures completed than requests queued."); 428 } 429 if (mInFlightPreviews == 0) { 430 mPreviewsEmpty.signalAll(); 431 } 432 } 433 434 private void onRequestCompleted(RequestHolder request, LegacyRequest legacyHolder, 435 long timestamp) { 436 mInFlight--; 437 if (DEBUG) { 438 Log.d(TAG, "Completed request " + request.getRequestId() + 439 ", " + mInFlight + " requests remain in flight."); 440 } 441 if (mInFlight < 0) { 442 throw new IllegalStateException( 443 "More captures completed than requests queued."); 444 } 445 mNotFull.signalAll(); 446 if (mInFlight == 0) { 447 mIsEmpty.signalAll(); 448 } 449 CameraMetadataNative result = mMapper.cachedConvertResultMetadata( 450 legacyHolder, timestamp); 451 mDeviceState.setCaptureResult(request, result); 452 } 453} 454