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 17 package com.android.testingcamera2; 18 19 import android.app.Activity; 20 import android.content.res.Configuration; 21 import android.graphics.Bitmap; 22 import android.graphics.BitmapFactory; 23 import android.graphics.ImageFormat; 24 import android.hardware.camera2.CameraCharacteristics; 25 import android.hardware.camera2.CameraDevice; 26 import android.hardware.camera2.CaptureFailure; 27 import android.hardware.camera2.CaptureRequest; 28 import android.hardware.camera2.CaptureResult; 29 import android.media.Image; 30 import android.os.AsyncTask; 31 import android.os.Bundle; 32 import android.os.Handler; 33 import android.util.Log; 34 import android.view.SurfaceHolder; 35 import android.view.SurfaceView; 36 import android.view.View; 37 import android.view.ViewGroup.LayoutParams; 38 import android.view.WindowManager; 39 import android.widget.Button; 40 import android.widget.ImageView; 41 import android.widget.SeekBar; 42 import android.widget.SeekBar.OnSeekBarChangeListener; 43 import android.widget.TextView; 44 import android.widget.ToggleButton; 45 46 import java.nio.ByteBuffer; 47 import java.util.HashSet; 48 import java.util.Set; 49 50 public class TestingCamera2 extends Activity implements SurfaceHolder.Callback { 51 52 private static final String TAG = "TestingCamera2"; 53 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 54 private CameraOps mCameraOps; 55 private static final int mSeekBarMax = 100; 56 private static final long MAX_EXPOSURE = 200000000L; // 200ms 57 private static final long MIN_EXPOSURE = 100000L; // 100us 58 private static final long MAX_FRAME_DURATION = 1000000000L; // 1s 59 // Manual control change step size 60 private static final int STEP_SIZE = 100; 61 // Min and max sensitivity ISO values 62 private static final int MIN_SENSITIVITY = 100; 63 private static final int MAX_SENSITIVITY = 1600; 64 private static final int ORIENTATION_UNINITIALIZED = -1; 65 66 private int mLastOrientation = ORIENTATION_UNINITIALIZED; 67 private SurfaceView mPreviewView; 68 private ImageView mStillView; 69 70 private SurfaceHolder mCurrentPreviewHolder = null; 71 72 private Button mInfoButton; 73 74 private SeekBar mSensitivityBar; 75 private SeekBar mExposureBar; 76 private SeekBar mFrameDurationBar; 77 78 private TextView mSensitivityInfoView; 79 private TextView mExposureInfoView; 80 private TextView mFrameDurationInfoView; 81 private TextView mCaptureResultView; 82 private ToggleButton mRecordingToggle; 83 private ToggleButton mManualCtrlToggle; 84 85 private CameraControls mCameraControl = null; 86 private final Set<View> mManualControls = new HashSet<View>(); 87 88 Handler mMainHandler; 89 90 @Override 91 public void onCreate(Bundle savedInstanceState) { 92 super.onCreate(savedInstanceState); 93 94 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 95 WindowManager.LayoutParams.FLAG_FULLSCREEN); 96 97 setContentView(R.layout.main); 98 99 mPreviewView = (SurfaceView) findViewById(R.id.preview_view); 100 mPreviewView.getHolder().addCallback(this); 101 102 mStillView = (ImageView) findViewById(R.id.still_view); 103 104 mInfoButton = (Button) findViewById(R.id.info_button); 105 mInfoButton.setOnClickListener(mInfoButtonListener); 106 mRecordingToggle = (ToggleButton) findViewById(R.id.start_recording); 107 mRecordingToggle.setOnClickListener(mRecordingToggleListener); 108 109 mManualCtrlToggle = (ToggleButton) findViewById(R.id.manual_control); 110 mManualCtrlToggle.setOnClickListener(mControlToggleListener); 111 112 mSensitivityBar = (SeekBar) findViewById(R.id.sensitivity_seekbar); 113 mSensitivityBar.setOnSeekBarChangeListener(mSensitivitySeekBarListener); 114 mSensitivityBar.setMax(mSeekBarMax); 115 mManualControls.add(mSensitivityBar); 116 117 mExposureBar = (SeekBar) findViewById(R.id.exposure_time_seekbar); 118 mExposureBar.setOnSeekBarChangeListener(mExposureSeekBarListener); 119 mExposureBar.setMax(mSeekBarMax); 120 mManualControls.add(mExposureBar); 121 122 mFrameDurationBar = (SeekBar) findViewById(R.id.frame_duration_seekbar); 123 mFrameDurationBar.setOnSeekBarChangeListener(mFrameDurationSeekBarListener); 124 mFrameDurationBar.setMax(mSeekBarMax); 125 mManualControls.add(mFrameDurationBar); 126 127 mSensitivityInfoView = (TextView) findViewById(R.id.sensitivity_bar_label); 128 mExposureInfoView = (TextView) findViewById(R.id.exposure_time_bar_label); 129 mFrameDurationInfoView = (TextView) findViewById(R.id.frame_duration_bar_label); 130 mCaptureResultView = (TextView) findViewById(R.id.capture_result_info_label); 131 132 enableManualControls(false); 133 mCameraControl = new CameraControls(); 134 135 // Get UI handler 136 mMainHandler = new Handler(); 137 138 try { 139 mCameraOps = CameraOps.create(this); 140 } catch(ApiFailureException e) { 141 logException("Cannot create camera ops!",e); 142 } 143 144 // Process the initial configuration (for i.e. initial orientation) 145 // We need this because #onConfigurationChanged doesn't get called when the app launches 146 maybeUpdateConfiguration(getResources().getConfiguration()); 147 } 148 149 @Override 150 public void onResume() { 151 super.onResume(); 152 try { 153 if (VERBOSE) Log.v(TAG, String.format("onResume")); 154 155 mCameraOps.minimalPreviewConfig(mPreviewView.getHolder()); 156 mCurrentPreviewHolder = mPreviewView.getHolder(); 157 } catch (ApiFailureException e) { 158 logException("Can't configure preview surface: ",e); 159 } 160 } 161 162 @Override 163 public void onPause() { 164 super.onPause(); 165 try { 166 if (VERBOSE) Log.v(TAG, String.format("onPause")); 167 168 mCameraOps.closeDevice(); 169 } catch (ApiFailureException e) { 170 logException("Can't close device: ",e); 171 } 172 mCurrentPreviewHolder = null; 173 } 174 175 @Override 176 public void onConfigurationChanged(Configuration newConfig) { 177 super.onConfigurationChanged(newConfig); 178 179 if (VERBOSE) { 180 Log.v(TAG, String.format("onConfiguredChanged: orientation %x", 181 newConfig.orientation)); 182 } 183 184 maybeUpdateConfiguration(newConfig); 185 } 186 187 private void maybeUpdateConfiguration(Configuration newConfig) { 188 if (VERBOSE) { 189 Log.v(TAG, String.format("handleConfiguration: orientation %x", 190 newConfig.orientation)); 191 } 192 193 if (mLastOrientation != newConfig.orientation) { 194 mLastOrientation = newConfig.orientation; 195 updatePreviewOrientation(); 196 } 197 } 198 199 private void updatePreviewOrientation() { 200 LayoutParams params = mPreviewView.getLayoutParams(); 201 int width = params.width; 202 int height = params.height; 203 204 if (VERBOSE) { 205 Log.v(TAG, String.format( 206 "onConfiguredChanged: current layout is %dx%d", width, 207 height)); 208 } 209 /** 210 * Force wide aspect ratios for landscape 211 * Force narrow aspect ratios for portrait 212 */ 213 if (mLastOrientation == Configuration.ORIENTATION_LANDSCAPE) { 214 if (height > width) { 215 int tmp = width; 216 width = height; 217 height = tmp; 218 } 219 } else if (mLastOrientation == Configuration.ORIENTATION_PORTRAIT) { 220 if (width > height) { 221 int tmp = width; 222 width = height; 223 height = tmp; 224 } 225 } 226 227 if (width != params.width && height != params.height) { 228 if (VERBOSE) { 229 Log.v(TAG, String.format( 230 "onConfiguredChanged: updating preview size to %dx%d", width, 231 height)); 232 } 233 params.width = width; 234 params.height = height; 235 236 mPreviewView.setLayoutParams(params); 237 } 238 } 239 240 /** SurfaceHolder.Callback methods */ 241 @Override 242 public void surfaceChanged(SurfaceHolder holder, 243 int format, 244 int width, 245 int height) { 246 if (VERBOSE) { 247 Log.v(TAG, String.format("surfaceChanged: format %x, width %d, height %d", format, 248 width, height)); 249 } 250 if (mCurrentPreviewHolder != null && holder == mCurrentPreviewHolder) { 251 try { 252 mCameraOps.minimalPreview(holder); 253 } catch (ApiFailureException e) { 254 logException("Can't start minimal preview: ", e); 255 } 256 } 257 } 258 259 @Override 260 public void surfaceCreated(SurfaceHolder holder) { 261 262 } 263 264 @Override 265 public void surfaceDestroyed(SurfaceHolder holder) { 266 } 267 268 private final Button.OnClickListener mInfoButtonListener = new Button.OnClickListener() { 269 @Override 270 public void onClick(View v) { 271 final Handler uiHandler = new Handler(); 272 AsyncTask.execute(new Runnable() { 273 @Override 274 public void run() { 275 try { 276 mCameraOps.minimalJpegCapture(mCaptureListener, mCaptureResultListener, 277 uiHandler, mCameraControl); 278 if (mCurrentPreviewHolder != null) { 279 mCameraOps.minimalPreview(mCurrentPreviewHolder); 280 } 281 } catch (ApiFailureException e) { 282 logException("Can't take a JPEG! ", e); 283 } 284 } 285 }); 286 } 287 }; 288 289 /** 290 * UI controls enable/disable for all manual controls 291 */ 292 private void enableManualControls(boolean enabled) { 293 for (View v : mManualControls) { 294 v.setEnabled(enabled); 295 } 296 } 297 298 private final CameraOps.CaptureListener mCaptureListener = new CameraOps.CaptureListener() { 299 @Override 300 public void onCaptureAvailable(Image capture) { 301 if (capture.getFormat() != ImageFormat.JPEG) { 302 Log.e(TAG, "Unexpected format: " + capture.getFormat()); 303 return; 304 } 305 ByteBuffer jpegBuffer = capture.getPlanes()[0].getBuffer(); 306 byte[] jpegData = new byte[jpegBuffer.capacity()]; 307 jpegBuffer.get(jpegData); 308 309 Bitmap b = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length); 310 mStillView.setImageBitmap(b); 311 } 312 }; 313 314 // TODO: this callback is not called for each capture, need figure out why. 315 private final CameraOps.CaptureResultListener mCaptureResultListener = 316 new CameraOps.CaptureResultListener() { 317 318 @Override 319 public void onCaptureStarted(CameraDevice camera, CaptureRequest request, 320 long timestamp) { 321 } 322 323 @Override 324 public void onCaptureCompleted( 325 CameraDevice camera, CaptureRequest request, CaptureResult result) { 326 Log.i(TAG, "Capture result is available"); 327 Integer reqCtrlMode; 328 Integer resCtrlMode; 329 if (request == null || result ==null) { 330 Log.e(TAG, "request/result is invalid"); 331 return; 332 } 333 Log.i(TAG, "Capture complete"); 334 final StringBuffer info = new StringBuffer("Capture Result:\n"); 335 336 reqCtrlMode = request.get(CaptureRequest.CONTROL_MODE); 337 resCtrlMode = result.get(CaptureResult.CONTROL_MODE); 338 info.append("Control mode: request " + reqCtrlMode + ". result " + resCtrlMode); 339 info.append("\n"); 340 341 Integer reqSen = request.get(CaptureRequest.SENSOR_SENSITIVITY); 342 Integer resSen = result.get(CaptureResult.SENSOR_SENSITIVITY); 343 info.append("Sensitivity: request " + reqSen + ". result " + resSen); 344 info.append("\n"); 345 346 Long reqExp = request.get(CaptureRequest.SENSOR_EXPOSURE_TIME); 347 Long resExp = result.get(CaptureResult.SENSOR_EXPOSURE_TIME); 348 info.append("Exposure: request " + reqExp + ". result " + resExp); 349 info.append("\n"); 350 351 Long reqFD = request.get(CaptureRequest.SENSOR_FRAME_DURATION); 352 Long resFD = result.get(CaptureResult.SENSOR_FRAME_DURATION); 353 info.append("Frame duration: request " + reqFD + ". result " + resFD); 354 info.append("\n"); 355 356 if (mMainHandler != null) { 357 mMainHandler.post (new Runnable() { 358 @Override 359 public void run() { 360 // Update UI for capture result 361 mCaptureResultView.setText(info); 362 } 363 }); 364 } 365 } 366 367 @Override 368 public void onCaptureFailed(CameraDevice camera, CaptureRequest request, 369 CaptureFailure failure) { 370 Log.e(TAG, "Capture failed"); 371 } 372 }; 373 374 private void logException(String msg, Throwable e) { 375 Log.e(TAG, msg + Log.getStackTraceString(e)); 376 } 377 378 private final OnSeekBarChangeListener mSensitivitySeekBarListener = 379 new OnSeekBarChangeListener() { 380 381 @Override 382 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 383 int[] defaultRange = {MIN_SENSITIVITY, MAX_SENSITIVITY}; 384 CameraCharacteristics properties = mCameraOps.getCameraCharacteristics(); 385 int[] sensitivityRange = properties.get( 386 CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE); 387 if (sensitivityRange == null || sensitivityRange.length < 2 || 388 sensitivityRange[0] > MIN_SENSITIVITY || sensitivityRange[1] < MAX_SENSITIVITY) { 389 Log.e(TAG, "unable to get sensitivity range, use default range"); 390 sensitivityRange = defaultRange; 391 } 392 int min = sensitivityRange[0]; 393 int max = sensitivityRange[1]; 394 float progressFactor = progress / (float)mSeekBarMax; 395 int curSensitivity = (int) (min + (max - min) * progressFactor); 396 curSensitivity = (curSensitivity / STEP_SIZE ) * STEP_SIZE; 397 mCameraControl.setSensitivity(curSensitivity); 398 // Update the sensitivity info 399 StringBuffer info = new StringBuffer("Sensitivity(ISO):"); 400 info.append("" + curSensitivity); 401 mSensitivityInfoView.setText(info); 402 mCameraOps.updatePreview(mCameraControl); 403 } 404 405 @Override 406 public void onStartTrackingTouch(SeekBar seekBar) { 407 } 408 409 @Override 410 public void onStopTrackingTouch(SeekBar seekBar) { 411 } 412 }; 413 414 private final OnSeekBarChangeListener mExposureSeekBarListener = 415 new OnSeekBarChangeListener() { 416 417 @Override 418 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 419 long[] defaultRange = {MIN_EXPOSURE, MAX_EXPOSURE}; 420 CameraCharacteristics properties = mCameraOps.getCameraCharacteristics(); 421 long[] exposureRange = properties.get( 422 CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE); 423 // Not enforce the max value check here, most of the devices don't support 424 // larger than 30s exposure time 425 if (exposureRange == null || exposureRange.length < 2 || 426 exposureRange[0] > MIN_EXPOSURE || exposureRange[1] < 0) { 427 exposureRange = defaultRange; 428 Log.e(TAG, "exposure time range is invalid, use default range"); 429 } 430 long min = exposureRange[0]; 431 long max = exposureRange[1]; 432 float progressFactor = progress / (float)mSeekBarMax; 433 long curExposureTime = (long) (min + (max - min) * progressFactor); 434 mCameraControl.setExposure(curExposureTime); 435 // Update the sensitivity info 436 StringBuffer info = new StringBuffer("Exposure Time:"); 437 info.append("" + curExposureTime / 1000000.0 + "ms"); 438 mExposureInfoView.setText(info); 439 mCameraOps.updatePreview(mCameraControl); 440 } 441 442 @Override 443 public void onStartTrackingTouch(SeekBar seekBar) { 444 } 445 446 @Override 447 public void onStopTrackingTouch(SeekBar seekBar) { 448 } 449 }; 450 451 private final OnSeekBarChangeListener mFrameDurationSeekBarListener = 452 new OnSeekBarChangeListener() { 453 454 @Override 455 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 456 CameraCharacteristics properties = mCameraOps.getCameraCharacteristics(); 457 Long frameDurationMax = properties.get( 458 CameraCharacteristics.SENSOR_INFO_MAX_FRAME_DURATION); 459 if (frameDurationMax == null || frameDurationMax <= 0) { 460 frameDurationMax = MAX_FRAME_DURATION; 461 Log.e(TAG, "max frame duration is invalid, set to " + frameDurationMax); 462 } 463 // Need calculate from different resolution, hard code to 10ms for now. 464 long min = 10000000L; 465 long max = frameDurationMax; 466 float progressFactor = progress / (float)mSeekBarMax; 467 long curFrameDuration = (long) (min + (max - min) * progressFactor); 468 mCameraControl.setFrameDuration(curFrameDuration); 469 // Update the sensitivity info 470 StringBuffer info = new StringBuffer("Frame Duration:"); 471 info.append("" + curFrameDuration / 1000000.0 + "ms"); 472 mFrameDurationInfoView.setText(info); 473 mCameraOps.updatePreview(mCameraControl); 474 } 475 476 @Override 477 public void onStartTrackingTouch(SeekBar seekBar) { 478 } 479 480 @Override 481 public void onStopTrackingTouch(SeekBar seekBar) { 482 } 483 }; 484 485 private final View.OnClickListener mControlToggleListener = 486 new View.OnClickListener() { 487 @Override 488 public void onClick(View v) { 489 boolean enableManual; 490 if (mManualCtrlToggle.isChecked()) { 491 enableManual = true; 492 } else { 493 enableManual = false; 494 } 495 mCameraControl.enableManualControl(enableManual); 496 enableManualControls(enableManual); 497 mCameraOps.updatePreview(mCameraControl); 498 } 499 }; 500 501 private final View.OnClickListener mRecordingToggleListener = 502 new View.OnClickListener() { 503 @Override 504 public void onClick(View v) { 505 if (mRecordingToggle.isChecked()) { 506 try { 507 mCameraOps.startRecording(/*useMediaCodec*/true); 508 } catch (ApiFailureException e) { 509 logException("Failed to start recording", e); 510 } 511 } else { 512 try { 513 mCameraOps.stopRecording(); 514 } catch (ApiFailureException e) { 515 logException("Failed to stop recording", e); 516 } 517 } 518 } 519 }; 520 } 521