ItsService.java revision d5a1394a36b958e81c20f8be8e2f1f72626d82da
1/* 2 * Copyright (C) 2013 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 com.android.camera2.its; 18 19import android.app.Service; 20import android.content.Context; 21import android.content.Intent; 22import android.graphics.ImageFormat; 23import android.hardware.camera2.CameraAccessException; 24import android.hardware.camera2.CameraDevice; 25import android.hardware.camera2.CameraManager; 26import android.hardware.camera2.CameraProperties; 27import android.hardware.camera2.CaptureRequest; 28import android.hardware.camera2.CaptureResult; 29import android.media.Image; 30import android.media.ImageReader; 31import android.net.Uri; 32import android.os.ConditionVariable; 33import android.os.Handler; 34import android.os.HandlerThread; 35import android.os.IBinder; 36import android.os.Message; 37import android.util.Log; 38import android.view.Surface; 39 40import org.json.JSONObject; 41 42import java.io.File; 43import java.nio.ByteBuffer; 44import java.util.ArrayList; 45import java.util.Arrays; 46import java.util.List; 47import java.util.concurrent.CountDownLatch; 48import java.util.concurrent.TimeUnit; 49 50public class ItsService extends Service { 51 public static final String TAG = ItsService.class.getSimpleName(); 52 public static final String PYTAG = "CAMERA-ITS-PY"; 53 54 // Supported intents 55 public static final String ACTION_CAPTURE = "com.android.camera2.its.CAPTURE"; 56 public static final String ACTION_3A = "com.android.camera2.its.3A"; 57 public static final String ACTION_GETPROPS = "com.android.camera2.its.GETPROPS"; 58 private static final int MESSAGE_CAPTURE = 1; 59 private static final int MESSAGE_3A = 2; 60 private static final int MESSAGE_GETPROPS = 3; 61 62 // Timeouts, in seconds. 63 public static final int TIMEOUT_CAPTURE = 10; 64 public static final int TIMEOUT_3A = 10; 65 66 private static final int MAX_CONCURRENT_READER_BUFFERS = 8; 67 68 public static final String REGION_KEY = "regions"; 69 public static final String REGION_AE_KEY = "ae"; 70 public static final String REGION_AWB_KEY = "awb"; 71 public static final String REGION_AF_KEY = "af"; 72 73 private CameraManager mCameraManager = null; 74 private CameraDevice mCamera = null; 75 private ImageReader mCaptureReader = null; 76 private CameraProperties mCameraProperties = null; 77 78 private HandlerThread mCommandThread; 79 private Handler mCommandHandler; 80 private HandlerThread mSaveThread; 81 private Handler mSaveHandler; 82 83 private ConditionVariable mInterlock3A = new ConditionVariable(true); 84 private volatile boolean mIssuedRequest3A = false; 85 private volatile boolean mConvergedAE = false; 86 private volatile boolean mConvergedAF = false; 87 private volatile boolean mConvergedAWB = false; 88 89 private CountDownLatch mCaptureCallbackLatch; 90 91 public interface CaptureListener { 92 void onCaptureAvailable(Image capture); 93 } 94 95 public abstract class CaptureResultListener extends CameraDevice.CaptureListener {} 96 97 @Override 98 public IBinder onBind(Intent intent) { 99 return null; 100 } 101 102 @Override 103 public void onCreate() { 104 105 try { 106 // Get handle to camera manager. 107 mCameraManager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE); 108 if (mCameraManager == null) { 109 throw new ItsException("Failed to connect to camera manager"); 110 } 111 112 // Open the camera device, and get its properties. 113 String[] devices; 114 try { 115 devices = mCameraManager.getCameraIdList(); 116 if (devices == null || devices.length == 0) { 117 throw new ItsException("No camera devices"); 118 } 119 120 // TODO: Add support for specifying which device to open. 121 mCamera = mCameraManager.openCamera(devices[0]); 122 mCameraProperties = mCamera.getProperties(); 123 } catch (CameraAccessException e) { 124 throw new ItsException("Failed to get device ID list"); 125 } 126 127 // Create a thread to receive images and save them. 128 mSaveThread = new HandlerThread("SaveThread"); 129 mSaveThread.start(); 130 mSaveHandler = new Handler(mSaveThread.getLooper()); 131 132 // Create a thread to process commands. 133 mCommandThread = new HandlerThread("CaptureThread"); 134 mCommandThread.start(); 135 mCommandHandler = new Handler(mCommandThread.getLooper(), new Handler.Callback() { 136 @Override 137 public boolean handleMessage(Message msg) { 138 try { 139 switch (msg.what) { 140 case MESSAGE_CAPTURE: 141 doCapture((Uri) msg.obj); 142 break; 143 case MESSAGE_3A: 144 do3A((Uri) msg.obj); 145 break; 146 case MESSAGE_GETPROPS: 147 doGetProps(); 148 break; 149 default: 150 throw new ItsException("Unknown message type"); 151 } 152 Log.i(PYTAG, "### DONE"); 153 return true; 154 } 155 catch (ItsException e) { 156 Log.e(TAG, "Script failed: ", e); 157 Log.e(PYTAG, "### FAIL"); 158 return true; 159 } 160 } 161 }); 162 } catch (ItsException e) { 163 Log.e(TAG, "Script failed: ", e); 164 Log.e(PYTAG, "### FAIL"); 165 } 166 } 167 168 @Override 169 public void onDestroy() { 170 try { 171 if (mCommandThread != null) { 172 mCommandThread.quit(); 173 mCommandThread = null; 174 } 175 if (mSaveThread != null) { 176 mSaveThread.quit(); 177 mSaveThread = null; 178 } 179 180 try { 181 mCamera.close(); 182 } catch (Exception e) { 183 throw new ItsException("Failed to close device"); 184 } 185 } catch (ItsException e) { 186 Log.e(TAG, "Script failed: ", e); 187 Log.e(PYTAG, "### FAIL"); 188 } 189 } 190 191 @Override 192 public int onStartCommand(Intent intent, int flags, int startId) { 193 try { 194 Log.i(PYTAG, "### RECV"); 195 String action = intent.getAction(); 196 if (ACTION_CAPTURE.equals(action)) { 197 Uri uri = intent.getData(); 198 Message m = mCommandHandler.obtainMessage(MESSAGE_CAPTURE, uri); 199 mCommandHandler.sendMessage(m); 200 } else if (ACTION_3A.equals(action)) { 201 Uri uri = intent.getData(); 202 Message m = mCommandHandler.obtainMessage(MESSAGE_3A, uri); 203 mCommandHandler.sendMessage(m); 204 } else if (ACTION_GETPROPS.equals(action)) { 205 Uri uri = intent.getData(); 206 Message m = mCommandHandler.obtainMessage(MESSAGE_GETPROPS, uri); 207 mCommandHandler.sendMessage(m); 208 } else { 209 throw new ItsException("Unhandled intent: " + intent.toString()); 210 } 211 } catch (ItsException e) { 212 Log.e(TAG, "Script failed: ", e); 213 Log.e(PYTAG, "### FAIL"); 214 } 215 return START_STICKY; 216 } 217 218 public void idleCamera() throws ItsException { 219 try { 220 mCamera.stopRepeating(); 221 mCamera.waitUntilIdle(); 222 } catch (CameraAccessException e) { 223 throw new ItsException("Error waiting for camera idle", e); 224 } 225 } 226 227 private ImageReader.OnImageAvailableListener 228 createAvailableListener(final CaptureListener listener) { 229 return new ImageReader.OnImageAvailableListener() { 230 @Override 231 public void onImageAvailable(ImageReader reader) { 232 Image i = null; 233 try { 234 i = reader.acquireNextImage(); 235 listener.onCaptureAvailable(i); 236 } catch(ImageReader.MaxImagesAcquiredException e) { 237 throw new IllegalStateException(e); 238 } finally { 239 if (i != null) { 240 i.close(); 241 } 242 } 243 } 244 }; 245 } 246 247 private ImageReader.OnImageAvailableListener 248 createAvailableListenerDropper(final CaptureListener listener) { 249 return new ImageReader.OnImageAvailableListener() { 250 @Override 251 public void onImageAvailable(ImageReader reader) { 252 try { 253 Image i = reader.acquireNextImage(); 254 i.close(); 255 } catch (ImageReader.MaxImagesAcquiredException e) { 256 // OK: don't need to drop it if we couldn't acquire it. 257 } 258 } 259 }; 260 } 261 262 private void doGetProps() throws ItsException { 263 String fileName = ItsUtils.getMetadataFileName(0); 264 File mdFile = ItsUtils.getOutputFile(ItsService.this, fileName); 265 ItsUtils.storeCameraProperties(mCameraProperties, mdFile); 266 Log.i(PYTAG, 267 String.format("### FILE %s", 268 ItsUtils.getExternallyVisiblePath(ItsService.this, mdFile.toString()))); 269 } 270 271 private void prepareCaptureReader(int width, int height, int format) { 272 if (mCaptureReader == null 273 || mCaptureReader.getWidth() != width 274 || mCaptureReader.getHeight() != height 275 || mCaptureReader.getImageFormat() != format) { 276 if (mCaptureReader != null) { 277 mCaptureReader.close(); 278 } 279 mCaptureReader = ImageReader.newInstance(width, height, format, 280 MAX_CONCURRENT_READER_BUFFERS); 281 } 282 } 283 284 private void do3A(Uri uri) throws ItsException { 285 try { 286 if (uri == null || !uri.toString().endsWith(".json")) { 287 throw new ItsException("Invalid URI: " + uri); 288 } 289 290 idleCamera(); 291 292 // Start a 3A action, and wait for it to converge. 293 // Get the converged values for each "A", and package into JSON result for caller. 294 295 // 3A happens on full-res frames. 296 android.hardware.camera2.Size sizes[] = mCameraProperties.get( 297 CameraProperties.SCALER_AVAILABLE_JPEG_SIZES); 298 int width = sizes[0].getWidth(); 299 int height = sizes[0].getHeight(); 300 int format = ImageFormat.YUV_420_888; 301 302 prepareCaptureReader(width, height, format); 303 List<Surface> outputSurfaces = new ArrayList<Surface>(1); 304 outputSurfaces.add(mCaptureReader.getSurface()); 305 mCamera.configureOutputs(outputSurfaces); 306 307 // Add a listener that just recycles buffers; they aren't saved anywhere. 308 ImageReader.OnImageAvailableListener readerListener = 309 createAvailableListenerDropper(mCaptureListener); 310 mCaptureReader.setOnImageAvailableListener(readerListener, mSaveHandler); 311 312 // Get the user-specified regions for AE, AWB, AF. 313 // Note that the user specifies normalized [x,y,w,h], which is converted below 314 // to an [x0,y0,x1,y1] region in sensor coords. The capture request region 315 // also has a fifth "weight" element: [x0,y0,x1,y1,w]. 316 int[] regionAE = new int[]{0,0,width-1,height-1,1}; 317 int[] regionAF = new int[]{0,0,width-1,height-1,1}; 318 int[] regionAWB = new int[]{0,0,width-1,height-1,1}; 319 JSONObject params = ItsUtils.loadJsonFile(uri); 320 if (params.has(REGION_KEY)) { 321 JSONObject regions = params.getJSONObject(REGION_KEY); 322 if (params.has(REGION_AE_KEY)) { 323 int[] r = ItsUtils.getJsonRectFromArray( 324 params.getJSONArray(REGION_AE_KEY), true, width, height); 325 regionAE = new int[]{r[0],r[1],r[0]+r[2]-1,r[1]+r[3]-1,1}; 326 } 327 if (params.has(REGION_AF_KEY)) { 328 int[] r = ItsUtils.getJsonRectFromArray( 329 params.getJSONArray(REGION_AF_KEY), true, width, height); 330 regionAF = new int[]{r[0],r[1],r[0]+r[2]-1,r[1]+r[3]-1,1}; 331 } 332 if (params.has(REGION_AWB_KEY)) { 333 int[] r = ItsUtils.getJsonRectFromArray( 334 params.getJSONArray(REGION_AWB_KEY), true, width, height); 335 regionAWB = new int[]{r[0],r[1],r[0]+r[2]-1,r[1]+r[3]-1,1}; 336 } 337 } 338 Log.i(TAG, "AE region: " + Arrays.toString(regionAE)); 339 Log.i(TAG, "AF region: " + Arrays.toString(regionAF)); 340 Log.i(TAG, "AWB region: " + Arrays.toString(regionAWB)); 341 342 mInterlock3A.open(); 343 mIssuedRequest3A = false; 344 mConvergedAE = false; 345 mConvergedAWB = false; 346 mConvergedAF = false; 347 long tstart = System.currentTimeMillis(); 348 boolean triggeredAE = false; 349 boolean triggeredAF = false; 350 351 // Keep issuing capture requests until 3A has converged. 352 // First do AE, then do AF and AWB together. 353 while (true) { 354 355 // Block until can take the next 3A frame. Only want one outstanding frame 356 // at a time, to simplify the logic here. 357 if (!mInterlock3A.block(TIMEOUT_3A * 1000) || 358 System.currentTimeMillis() - tstart > TIMEOUT_3A * 1000) { 359 throw new ItsException("3A failed to converge (timeout)"); 360 } 361 mInterlock3A.close(); 362 363 // If not converged yet, issue another capture request. 364 if (!mConvergedAE || !mConvergedAWB || !mConvergedAF) { 365 366 // Baseline capture request for 3A. 367 CaptureRequest.Builder req = mCamera.createCaptureRequest( 368 CameraDevice.TEMPLATE_PREVIEW); 369 req.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); 370 req.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); 371 req.set(CaptureRequest.CONTROL_CAPTURE_INTENT, 372 CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW); 373 req.set(CaptureRequest.CONTROL_AE_MODE, 374 CaptureRequest.CONTROL_AE_MODE_ON); 375 req.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 0); 376 req.set(CaptureRequest.CONTROL_AE_LOCK, false); 377 req.set(CaptureRequest.CONTROL_AE_REGIONS, regionAE); 378 req.set(CaptureRequest.CONTROL_AF_MODE, 379 CaptureRequest.CONTROL_AF_MODE_AUTO); 380 req.set(CaptureRequest.CONTROL_AF_REGIONS, regionAF); 381 req.set(CaptureRequest.CONTROL_AWB_MODE, 382 CaptureRequest.CONTROL_AWB_MODE_AUTO); 383 req.set(CaptureRequest.CONTROL_AWB_LOCK, false); 384 req.set(CaptureRequest.CONTROL_AWB_REGIONS, regionAWB); 385 386 // Trigger AE first. 387 if (!triggeredAE) { 388 Log.i(TAG, "Triggering AE"); 389 req.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, 390 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); 391 triggeredAE = true; 392 } 393 394 // After AE has converged, trigger AF. 395 if (!triggeredAF && triggeredAE && mConvergedAE) { 396 Log.i(TAG, "Triggering AF"); 397 req.set(CaptureRequest.CONTROL_AF_TRIGGER, 398 CaptureRequest.CONTROL_AF_TRIGGER_START); 399 triggeredAF = true; 400 } 401 402 req.addTarget(mCaptureReader.getSurface()); 403 404 mIssuedRequest3A = true; 405 mCamera.capture(req.build(), mCaptureResultListener, null); 406 } else { 407 Log.i(TAG, "3A converged"); 408 break; 409 } 410 } 411 } catch (android.hardware.camera2.CameraAccessException e) { 412 throw new ItsException("Access error: ", e); 413 } catch (org.json.JSONException e) { 414 throw new ItsException("JSON error: ", e); 415 } 416 } 417 418 private void doCapture(Uri uri) throws ItsException { 419 try { 420 if (uri == null || !uri.toString().endsWith(".json")) { 421 throw new ItsException("Invalid URI: " + uri); 422 } 423 424 idleCamera(); 425 426 // Parse the JSON to get the list of capture requests. 427 List<CaptureRequest.Builder> requests = ItsUtils.loadRequestList(mCamera, uri); 428 429 // Set the output surface and listeners. 430 try { 431 // Default: 432 // Capture full-frame images. Use the reported JPEG size rather than the sensor 433 // size since this is more likely to be the unscaled size; the crop from sensor 434 // size is probably for the ISP (e.g. demosaicking) rather than the encoder. 435 android.hardware.camera2.Size sizes[] = mCameraProperties.get( 436 CameraProperties.SCALER_AVAILABLE_JPEG_SIZES); 437 int width = sizes[0].getWidth(); 438 int height = sizes[0].getHeight(); 439 int format = ImageFormat.YUV_420_888; 440 441 JSONObject jsonOutputSpecs = ItsUtils.getOutputSpecs(uri); 442 if (jsonOutputSpecs != null) { 443 // Use the user's JSON capture spec. 444 int width2 = jsonOutputSpecs.optInt("width"); 445 int height2 = jsonOutputSpecs.optInt("height"); 446 if (width2 > 0) { 447 width = width2; 448 } 449 if (height2 > 0) { 450 height = height2; 451 } 452 String sformat = jsonOutputSpecs.optString("format"); 453 if ("yuv".equals(sformat)) { 454 format = ImageFormat.YUV_420_888; 455 } else if ("jpg".equals(sformat) || "jpeg".equals(sformat)) { 456 format = ImageFormat.JPEG; 457 } else if ("".equals(sformat)) { 458 // No format specified. 459 } else { 460 throw new ItsException("Unsupported format: " + sformat); 461 } 462 } 463 464 Log.i(PYTAG, String.format("### SIZE %d %d", width, height)); 465 466 prepareCaptureReader(width, height, format); 467 List<Surface> outputSurfaces = new ArrayList<Surface>(1); 468 outputSurfaces.add(mCaptureReader.getSurface()); 469 mCamera.configureOutputs(outputSurfaces); 470 471 ImageReader.OnImageAvailableListener readerListener = 472 createAvailableListener(mCaptureListener); 473 mCaptureReader.setOnImageAvailableListener(readerListener, mSaveHandler); 474 475 // Plan for how many callbacks need to be received throughout the duration of this 476 // sequence of capture requests. 477 int numCaptures = requests.size(); 478 mCaptureCallbackLatch = new CountDownLatch( 479 numCaptures * ItsUtils.getCallbacksPerCapture(format)); 480 481 } catch (CameraAccessException e) { 482 throw new ItsException("Error configuring outputs", e); 483 } 484 485 // Initiate the captures. 486 for (int i = 0; i < requests.size(); i++) { 487 CaptureRequest.Builder req = requests.get(i); 488 Log.i(PYTAG, String.format("### CAPT %d of %d", i+1, requests.size())); 489 req.addTarget(mCaptureReader.getSurface()); 490 mCamera.capture(req.build(), mCaptureResultListener, null); 491 } 492 493 // Make sure all callbacks have been hit (wait until captures are done). 494 try { 495 if (!mCaptureCallbackLatch.await(TIMEOUT_CAPTURE, TimeUnit.SECONDS)) { 496 throw new ItsException( 497 "Timeout hit, but all callbacks not received"); 498 } 499 } catch (InterruptedException e) { 500 throw new ItsException("Interrupted: ", e); 501 } 502 503 } catch (android.hardware.camera2.CameraAccessException e) { 504 throw new ItsException("Access error: ", e); 505 } 506 } 507 508 private final CaptureListener mCaptureListener = new CaptureListener() { 509 @Override 510 public void onCaptureAvailable(Image capture) { 511 try { 512 int format = capture.getFormat(); 513 String extFileName = null; 514 if (format == ImageFormat.JPEG) { 515 String fileName = ItsUtils.getJpegFileName(capture.getTimestamp()); 516 ByteBuffer buf = capture.getPlanes()[0].getBuffer(); 517 extFileName = ItsUtils.writeImageToFile(ItsService.this, buf, fileName); 518 } else if (format == ImageFormat.YUV_420_888) { 519 String fileName = ItsUtils.getYuvFileName(capture.getTimestamp()); 520 byte[] img = ItsUtils.getDataFromImage(capture); 521 ByteBuffer buf = ByteBuffer.wrap(img); 522 extFileName = ItsUtils.writeImageToFile(ItsService.this, buf, fileName); 523 } else { 524 throw new ItsException("Unsupported image format: " + format); 525 } 526 Log.i(PYTAG, String.format("### FILE %s", extFileName)); 527 mCaptureCallbackLatch.countDown(); 528 } catch (ItsException e) { 529 Log.e(TAG, "Script error: " + e); 530 Log.e(PYTAG, "### FAIL"); 531 } 532 } 533 }; 534 535 private final CaptureResultListener mCaptureResultListener = new CaptureResultListener() { 536 @Override 537 public void onCaptureStarted(CameraDevice camera, CaptureRequest request, long timestamp) { 538 } 539 540 @Override 541 public void onCaptureCompleted(CameraDevice camera, CaptureRequest request, 542 CaptureResult result) { 543 try { 544 // Currently result has all 0 values. 545 if (request == null || result == null) { 546 throw new ItsException("Request/result is invalid"); 547 } 548 549 Log.i(TAG, String.format( 550 "Capture result: AE=%d, AF=%d, AWB=%d, sens=%d, exp=%dms, dur=%dms", 551 result.get(CaptureResult.CONTROL_AE_STATE), 552 result.get(CaptureResult.CONTROL_AF_STATE), 553 result.get(CaptureResult.CONTROL_AWB_STATE), 554 result.get(CaptureResult.SENSOR_SENSITIVITY), 555 result.get(CaptureResult.SENSOR_EXPOSURE_TIME).intValue(), 556 result.get(CaptureResult.SENSOR_FRAME_DURATION).intValue())); 557 558 mConvergedAE = result.get(CaptureResult.CONTROL_AE_STATE) == 559 CaptureResult.CONTROL_AE_STATE_CONVERGED; 560 mConvergedAF = result.get(CaptureResult.CONTROL_AF_STATE) == 561 CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED; 562 mConvergedAWB = result.get(CaptureResult.CONTROL_AWB_STATE) == 563 CaptureResult.CONTROL_AWB_STATE_CONVERGED; 564 565 if (mIssuedRequest3A) { 566 mIssuedRequest3A = false; 567 mInterlock3A.open(); 568 } else { 569 String fileName = ItsUtils.getMetadataFileName( 570 result.get(CaptureResult.SENSOR_TIMESTAMP)); 571 File mdFile = ItsUtils.getOutputFile(ItsService.this, fileName); 572 ItsUtils.storeResults(mCameraProperties, request, result, mdFile); 573 mCaptureCallbackLatch.countDown(); 574 } 575 } catch (ItsException e) { 576 Log.e(TAG, "Script error: " + e); 577 Log.e(PYTAG, "### FAIL"); 578 } catch (Exception e) { 579 Log.e(TAG, "Script error: " + e); 580 Log.e(PYTAG, "### FAIL"); 581 } 582 } 583 584 @Override 585 public void onCaptureFailed(CameraDevice camera, CaptureRequest request) { 586 mCaptureCallbackLatch.countDown(); 587 Log.e(TAG, "Script error: capture failed"); 588 Log.e(PYTAG, "### FAIL"); 589 } 590 }; 591 592} 593