CameraDeviceUserShim.java revision 385f9e2146d2600ae9fd20053aab8ee5abcac9a6
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 17package android.hardware.camera2.legacy; 18 19import android.hardware.ICameraService; 20import android.hardware.Camera; 21import android.hardware.Camera.CameraInfo; 22import android.hardware.camera2.CameraAccessException; 23import android.hardware.camera2.CameraCharacteristics; 24import android.hardware.camera2.CaptureRequest; 25import android.hardware.camera2.ICameraDeviceCallbacks; 26import android.hardware.camera2.ICameraDeviceUser; 27import android.hardware.camera2.impl.CameraMetadataNative; 28import android.hardware.camera2.impl.CaptureResultExtras; 29import android.hardware.camera2.params.OutputConfiguration; 30import android.hardware.camera2.utils.SubmitInfo; 31import android.os.ConditionVariable; 32import android.os.IBinder; 33import android.os.Looper; 34import android.os.Handler; 35import android.os.HandlerThread; 36import android.os.Message; 37import android.os.RemoteException; 38import android.os.ServiceSpecificException; 39import android.util.Log; 40import android.util.SparseArray; 41import android.view.Surface; 42 43import java.util.ArrayList; 44import java.util.List; 45 46import static android.system.OsConstants.EACCES; 47import static android.system.OsConstants.ENODEV; 48 49/** 50 * Compatibility implementation of the Camera2 API binder interface. 51 * 52 * <p> 53 * This is intended to be called from the same process as client 54 * {@link android.hardware.camera2.CameraDevice}, and wraps a 55 * {@link android.hardware.camera2.legacy.LegacyCameraDevice} that emulates Camera2 service using 56 * the Camera1 API. 57 * </p> 58 * 59 * <p> 60 * Keep up to date with ICameraDeviceUser.aidl. 61 * </p> 62 */ 63@SuppressWarnings("deprecation") 64public class CameraDeviceUserShim implements ICameraDeviceUser { 65 private static final String TAG = "CameraDeviceUserShim"; 66 67 private static final boolean DEBUG = false; 68 private static final int OPEN_CAMERA_TIMEOUT_MS = 5000; // 5 sec (same as api1 cts timeout) 69 70 private final LegacyCameraDevice mLegacyDevice; 71 72 private final Object mConfigureLock = new Object(); 73 private int mSurfaceIdCounter; 74 private boolean mConfiguring; 75 private final SparseArray<Surface> mSurfaces; 76 private final CameraCharacteristics mCameraCharacteristics; 77 private final CameraLooper mCameraInit; 78 private final CameraCallbackThread mCameraCallbacks; 79 80 81 protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera, 82 CameraCharacteristics characteristics, CameraLooper cameraInit, 83 CameraCallbackThread cameraCallbacks) { 84 mLegacyDevice = legacyCamera; 85 mConfiguring = false; 86 mSurfaces = new SparseArray<Surface>(); 87 mCameraCharacteristics = characteristics; 88 mCameraInit = cameraInit; 89 mCameraCallbacks = cameraCallbacks; 90 91 mSurfaceIdCounter = 0; 92 } 93 94 private static int translateErrorsFromCamera1(int errorCode) { 95 if (errorCode == -EACCES) { 96 return ICameraService.ERROR_PERMISSION_DENIED; 97 } 98 99 return errorCode; 100 } 101 102 /** 103 * Create a separate looper/thread for the camera to run on; open the camera. 104 * 105 * <p>Since the camera automatically latches on to the current thread's looper, 106 * it's important that we have our own thread with our own looper to guarantee 107 * that the camera callbacks get correctly posted to our own thread.</p> 108 */ 109 private static class CameraLooper implements Runnable, AutoCloseable { 110 private final int mCameraId; 111 private Looper mLooper; 112 private volatile int mInitErrors; 113 private final Camera mCamera = Camera.openUninitialized(); 114 private final ConditionVariable mStartDone = new ConditionVariable(); 115 private final Thread mThread; 116 117 /** 118 * Spin up a new thread, immediately open the camera in the background. 119 * 120 * <p>Use {@link #waitForOpen} to block until the camera is finished opening.</p> 121 * 122 * @param cameraId numeric camera Id 123 * 124 * @see #waitForOpen 125 */ 126 public CameraLooper(int cameraId) { 127 mCameraId = cameraId; 128 129 mThread = new Thread(this); 130 mThread.start(); 131 } 132 133 public Camera getCamera() { 134 return mCamera; 135 } 136 137 @Override 138 public void run() { 139 // Set up a looper to be used by camera. 140 Looper.prepare(); 141 142 // Save the looper so that we can terminate this thread 143 // after we are done with it. 144 mLooper = Looper.myLooper(); 145 mInitErrors = mCamera.cameraInitUnspecified(mCameraId); 146 mStartDone.open(); 147 Looper.loop(); // Blocks forever until #close is called. 148 } 149 150 /** 151 * Quit the looper safely; then join until the thread shuts down. 152 */ 153 @Override 154 public void close() { 155 if (mLooper == null) { 156 return; 157 } 158 159 mLooper.quitSafely(); 160 try { 161 mThread.join(); 162 } catch (InterruptedException e) { 163 throw new AssertionError(e); 164 } 165 166 mLooper = null; 167 } 168 169 /** 170 * Block until the camera opens; then return its initialization error code (if any). 171 * 172 * @param timeoutMs timeout in milliseconds 173 * 174 * @return int error code 175 * 176 * @throws ServiceSpecificException if the camera open times out with ({@code CAMERA_ERROR}) 177 */ 178 public int waitForOpen(int timeoutMs) { 179 // Block until the camera is open asynchronously 180 if (!mStartDone.block(timeoutMs)) { 181 Log.e(TAG, "waitForOpen - Camera failed to open after timeout of " 182 + OPEN_CAMERA_TIMEOUT_MS + " ms"); 183 try { 184 mCamera.release(); 185 } catch (RuntimeException e) { 186 Log.e(TAG, "connectBinderShim - Failed to release camera after timeout ", e); 187 } 188 189 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION); 190 } 191 192 return mInitErrors; 193 } 194 } 195 196 /** 197 * A thread to process callbacks to send back to the camera client. 198 * 199 * <p>This effectively emulates one-way binder semantics when in the same process as the 200 * callee.</p> 201 */ 202 private static class CameraCallbackThread implements ICameraDeviceCallbacks { 203 private static final int CAMERA_ERROR = 0; 204 private static final int CAMERA_IDLE = 1; 205 private static final int CAPTURE_STARTED = 2; 206 private static final int RESULT_RECEIVED = 3; 207 private static final int PREPARED = 4; 208 209 private final HandlerThread mHandlerThread; 210 private Handler mHandler; 211 212 private final ICameraDeviceCallbacks mCallbacks; 213 214 public CameraCallbackThread(ICameraDeviceCallbacks callbacks) { 215 mCallbacks = callbacks; 216 217 mHandlerThread = new HandlerThread("LegacyCameraCallback"); 218 mHandlerThread.start(); 219 } 220 221 public void close() { 222 mHandlerThread.quitSafely(); 223 } 224 225 @Override 226 public void onDeviceError(final int errorCode, final CaptureResultExtras resultExtras) { 227 Message msg = getHandler().obtainMessage(CAMERA_ERROR, 228 /*arg1*/ errorCode, /*arg2*/ 0, 229 /*obj*/ resultExtras); 230 getHandler().sendMessage(msg); 231 } 232 233 @Override 234 public void onDeviceIdle() { 235 Message msg = getHandler().obtainMessage(CAMERA_IDLE); 236 getHandler().sendMessage(msg); 237 } 238 239 @Override 240 public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) { 241 Message msg = getHandler().obtainMessage(CAPTURE_STARTED, 242 /*arg1*/ (int) (timestamp & 0xFFFFFFFFL), 243 /*arg2*/ (int) ( (timestamp >> 32) & 0xFFFFFFFFL), 244 /*obj*/ resultExtras); 245 getHandler().sendMessage(msg); 246 } 247 248 @Override 249 public void onResultReceived(final CameraMetadataNative result, 250 final CaptureResultExtras resultExtras) { 251 Object[] resultArray = new Object[] { result, resultExtras }; 252 Message msg = getHandler().obtainMessage(RESULT_RECEIVED, 253 /*obj*/ resultArray); 254 getHandler().sendMessage(msg); 255 } 256 257 @Override 258 public void onPrepared(int streamId) { 259 Message msg = getHandler().obtainMessage(PREPARED, 260 /*arg1*/ streamId, /*arg2*/ 0); 261 getHandler().sendMessage(msg); 262 } 263 264 @Override 265 public IBinder asBinder() { 266 // This is solely intended to be used for in-process binding. 267 return null; 268 } 269 270 private Handler getHandler() { 271 if (mHandler == null) { 272 mHandler = new CallbackHandler(mHandlerThread.getLooper()); 273 } 274 return mHandler; 275 } 276 277 private class CallbackHandler extends Handler { 278 public CallbackHandler(Looper l) { 279 super(l); 280 } 281 282 @Override 283 public void handleMessage(Message msg) { 284 try { 285 switch (msg.what) { 286 case CAMERA_ERROR: { 287 int errorCode = msg.arg1; 288 CaptureResultExtras resultExtras = (CaptureResultExtras) msg.obj; 289 mCallbacks.onDeviceError(errorCode, resultExtras); 290 break; 291 } 292 case CAMERA_IDLE: 293 mCallbacks.onDeviceIdle(); 294 break; 295 case CAPTURE_STARTED: { 296 long timestamp = msg.arg2 & 0xFFFFFFFFL; 297 timestamp = (timestamp << 32) | (msg.arg1 & 0xFFFFFFFFL); 298 CaptureResultExtras resultExtras = (CaptureResultExtras) msg.obj; 299 mCallbacks.onCaptureStarted(resultExtras, timestamp); 300 break; 301 } 302 case RESULT_RECEIVED: { 303 Object[] resultArray = (Object[]) msg.obj; 304 CameraMetadataNative result = (CameraMetadataNative) resultArray[0]; 305 CaptureResultExtras resultExtras = (CaptureResultExtras) resultArray[1]; 306 mCallbacks.onResultReceived(result, resultExtras); 307 break; 308 } 309 case PREPARED: { 310 int streamId = msg.arg1; 311 mCallbacks.onPrepared(streamId); 312 break; 313 } 314 default: 315 throw new IllegalArgumentException( 316 "Unknown callback message " + msg.what); 317 } 318 } catch (RemoteException e) { 319 throw new IllegalStateException( 320 "Received remote exception during camera callback " + msg.what, e); 321 } 322 } 323 } 324 } 325 326 public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks, 327 int cameraId) { 328 if (DEBUG) { 329 Log.d(TAG, "Opening shim Camera device"); 330 } 331 332 /* 333 * Put the camera open on a separate thread with its own looper; otherwise 334 * if the main thread is used then the callbacks might never get delivered 335 * (e.g. in CTS which run its own default looper only after tests) 336 */ 337 338 CameraLooper init = new CameraLooper(cameraId); 339 340 CameraCallbackThread threadCallbacks = new CameraCallbackThread(callbacks); 341 342 // TODO: Make this async instead of blocking 343 int initErrors = init.waitForOpen(OPEN_CAMERA_TIMEOUT_MS); 344 Camera legacyCamera = init.getCamera(); 345 346 // Check errors old HAL initialization 347 LegacyExceptionUtils.throwOnServiceError(initErrors); 348 349 // Disable shutter sounds (this will work unconditionally) for api2 clients 350 legacyCamera.disableShutterSound(); 351 352 CameraInfo info = new CameraInfo(); 353 Camera.getCameraInfo(cameraId, info); 354 355 Camera.Parameters legacyParameters = null; 356 try { 357 legacyParameters = legacyCamera.getParameters(); 358 } catch (RuntimeException e) { 359 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, 360 "Unable to get initial parameters: " + e.getMessage()); 361 } 362 363 CameraCharacteristics characteristics = 364 LegacyMetadataMapper.createCharacteristics(legacyParameters, info); 365 LegacyCameraDevice device = new LegacyCameraDevice( 366 cameraId, legacyCamera, characteristics, threadCallbacks); 367 return new CameraDeviceUserShim(cameraId, device, characteristics, init, threadCallbacks); 368 } 369 370 @Override 371 public void disconnect() { 372 if (DEBUG) { 373 Log.d(TAG, "disconnect called."); 374 } 375 376 if (mLegacyDevice.isClosed()) { 377 Log.w(TAG, "Cannot disconnect, device has already been closed."); 378 } 379 380 try { 381 mLegacyDevice.close(); 382 } finally { 383 mCameraInit.close(); 384 mCameraCallbacks.close(); 385 } 386 } 387 388 @Override 389 public SubmitInfo submitRequest(CaptureRequest request, boolean streaming) { 390 if (DEBUG) { 391 Log.d(TAG, "submitRequest called."); 392 } 393 if (mLegacyDevice.isClosed()) { 394 String err = "Cannot submit request, device has been closed."; 395 Log.e(TAG, err); 396 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 397 } 398 399 synchronized(mConfigureLock) { 400 if (mConfiguring) { 401 String err = "Cannot submit request, configuration change in progress."; 402 Log.e(TAG, err); 403 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 404 } 405 } 406 return mLegacyDevice.submitRequest(request, streaming); 407 } 408 409 @Override 410 public SubmitInfo submitRequestList(CaptureRequest[] request, boolean streaming) { 411 if (DEBUG) { 412 Log.d(TAG, "submitRequestList called."); 413 } 414 if (mLegacyDevice.isClosed()) { 415 String err = "Cannot submit request list, device has been closed."; 416 Log.e(TAG, err); 417 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 418 } 419 420 synchronized(mConfigureLock) { 421 if (mConfiguring) { 422 String err = "Cannot submit request, configuration change in progress."; 423 Log.e(TAG, err); 424 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 425 } 426 } 427 return mLegacyDevice.submitRequestList(request, streaming); 428 } 429 430 @Override 431 public long cancelRequest(int requestId) { 432 if (DEBUG) { 433 Log.d(TAG, "cancelRequest called."); 434 } 435 if (mLegacyDevice.isClosed()) { 436 String err = "Cannot cancel request, device has been closed."; 437 Log.e(TAG, err); 438 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 439 } 440 441 synchronized(mConfigureLock) { 442 if (mConfiguring) { 443 String err = "Cannot cancel request, configuration change in progress."; 444 Log.e(TAG, err); 445 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 446 } 447 } 448 return mLegacyDevice.cancelRequest(requestId); 449 } 450 451 @Override 452 public void beginConfigure() { 453 if (DEBUG) { 454 Log.d(TAG, "beginConfigure called."); 455 } 456 if (mLegacyDevice.isClosed()) { 457 String err = "Cannot begin configure, device has been closed."; 458 Log.e(TAG, err); 459 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 460 } 461 462 synchronized(mConfigureLock) { 463 if (mConfiguring) { 464 String err = "Cannot begin configure, configuration change already in progress."; 465 Log.e(TAG, err); 466 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 467 } 468 mConfiguring = true; 469 } 470 } 471 472 @Override 473 public void endConfigure(boolean isConstrainedHighSpeed) { 474 if (DEBUG) { 475 Log.d(TAG, "endConfigure called."); 476 } 477 if (mLegacyDevice.isClosed()) { 478 String err = "Cannot end configure, device has been closed."; 479 Log.e(TAG, err); 480 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 481 } 482 483 SparseArray<Surface> surfaces = null; 484 synchronized(mConfigureLock) { 485 if (!mConfiguring) { 486 String err = "Cannot end configure, no configuration change in progress."; 487 Log.e(TAG, err); 488 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 489 } 490 if (mSurfaces != null) { 491 surfaces = mSurfaces.clone(); 492 } 493 mConfiguring = false; 494 } 495 mLegacyDevice.configureOutputs(surfaces); 496 } 497 498 @Override 499 public void deleteStream(int streamId) { 500 if (DEBUG) { 501 Log.d(TAG, "deleteStream called."); 502 } 503 if (mLegacyDevice.isClosed()) { 504 String err = "Cannot delete stream, device has been closed."; 505 Log.e(TAG, err); 506 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 507 } 508 509 synchronized(mConfigureLock) { 510 if (!mConfiguring) { 511 String err = "Cannot delete stream, no configuration change in progress."; 512 Log.e(TAG, err); 513 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 514 } 515 int index = mSurfaces.indexOfKey(streamId); 516 if (index < 0) { 517 String err = "Cannot delete stream, stream id " + streamId + " doesn't exist."; 518 Log.e(TAG, err); 519 throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err); 520 } 521 mSurfaces.removeAt(index); 522 } 523 } 524 525 @Override 526 public int createStream(OutputConfiguration outputConfiguration) { 527 if (DEBUG) { 528 Log.d(TAG, "createStream called."); 529 } 530 if (mLegacyDevice.isClosed()) { 531 String err = "Cannot create stream, device has been closed."; 532 Log.e(TAG, err); 533 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 534 } 535 536 synchronized(mConfigureLock) { 537 if (!mConfiguring) { 538 String err = "Cannot create stream, beginConfigure hasn't been called yet."; 539 Log.e(TAG, err); 540 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 541 } 542 if (outputConfiguration.getRotation() != OutputConfiguration.ROTATION_0) { 543 String err = "Cannot create stream, stream rotation is not supported."; 544 Log.e(TAG, err); 545 throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err); 546 } 547 int id = ++mSurfaceIdCounter; 548 mSurfaces.put(id, outputConfiguration.getSurface()); 549 return id; 550 } 551 } 552 553 @Override 554 public int createInputStream(int width, int height, int format) { 555 String err = "Creating input stream is not supported on legacy devices"; 556 Log.e(TAG, err); 557 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 558 } 559 560 @Override 561 public Surface getInputSurface() { 562 String err = "Getting input surface is not supported on legacy devices"; 563 Log.e(TAG, err); 564 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 565 } 566 567 @Override 568 public CameraMetadataNative createDefaultRequest(int templateId) { 569 if (DEBUG) { 570 Log.d(TAG, "createDefaultRequest called."); 571 } 572 if (mLegacyDevice.isClosed()) { 573 String err = "Cannot create default request, device has been closed."; 574 Log.e(TAG, err); 575 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 576 } 577 578 CameraMetadataNative template; 579 try { 580 template = 581 LegacyMetadataMapper.createRequestTemplate(mCameraCharacteristics, templateId); 582 } catch (IllegalArgumentException e) { 583 String err = "createDefaultRequest - invalid templateId specified"; 584 Log.e(TAG, err); 585 throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err); 586 } 587 588 return template; 589 } 590 591 @Override 592 public CameraMetadataNative getCameraInfo() { 593 if (DEBUG) { 594 Log.d(TAG, "getCameraInfo called."); 595 } 596 // TODO: implement getCameraInfo. 597 Log.e(TAG, "getCameraInfo unimplemented."); 598 return null; 599 } 600 601 @Override 602 public void waitUntilIdle() throws RemoteException { 603 if (DEBUG) { 604 Log.d(TAG, "waitUntilIdle called."); 605 } 606 if (mLegacyDevice.isClosed()) { 607 String err = "Cannot wait until idle, device has been closed."; 608 Log.e(TAG, err); 609 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 610 } 611 612 synchronized(mConfigureLock) { 613 if (mConfiguring) { 614 String err = "Cannot wait until idle, configuration change in progress."; 615 Log.e(TAG, err); 616 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 617 } 618 } 619 mLegacyDevice.waitUntilIdle(); 620 } 621 622 @Override 623 public long flush() { 624 if (DEBUG) { 625 Log.d(TAG, "flush called."); 626 } 627 if (mLegacyDevice.isClosed()) { 628 String err = "Cannot flush, device has been closed."; 629 Log.e(TAG, err); 630 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 631 } 632 633 synchronized(mConfigureLock) { 634 if (mConfiguring) { 635 String err = "Cannot flush, configuration change in progress."; 636 Log.e(TAG, err); 637 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 638 } 639 } 640 return mLegacyDevice.flush(); 641 } 642 643 public void prepare(int streamId) { 644 if (DEBUG) { 645 Log.d(TAG, "prepare called."); 646 } 647 if (mLegacyDevice.isClosed()) { 648 String err = "Cannot prepare stream, device has been closed."; 649 Log.e(TAG, err); 650 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 651 } 652 653 // LEGACY doesn't support actual prepare, just signal success right away 654 mCameraCallbacks.onPrepared(streamId); 655 } 656 657 public void prepare2(int maxCount, int streamId) { 658 // We don't support this in LEGACY mode. 659 prepare(streamId); 660 } 661 662 public void tearDown(int streamId) { 663 if (DEBUG) { 664 Log.d(TAG, "tearDown called."); 665 } 666 if (mLegacyDevice.isClosed()) { 667 String err = "Cannot tear down stream, device has been closed."; 668 Log.e(TAG, err); 669 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 670 } 671 672 // LEGACY doesn't support actual teardown, so just a no-op 673 } 674 675 @Override 676 public IBinder asBinder() { 677 // This is solely intended to be used for in-process binding. 678 return null; 679 } 680} 681