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