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.v1; 18 19 import android.Manifest; 20 import android.app.Activity; 21 import android.content.pm.PackageManager; 22 import android.content.res.Configuration; 23 import android.graphics.Bitmap; 24 import android.graphics.BitmapFactory; 25 import android.graphics.ImageFormat; 26 import android.hardware.camera2.CameraCharacteristics; 27 import android.hardware.camera2.CameraCaptureSession; 28 import android.hardware.camera2.CameraDevice; 29 import android.hardware.camera2.CaptureFailure; 30 import android.hardware.camera2.CaptureRequest; 31 import android.hardware.camera2.CaptureResult; 32 import android.hardware.camera2.params.StreamConfigurationMap; 33 import android.hardware.camera2.TotalCaptureResult; 34 import android.media.Image; 35 import android.media.MediaMuxer; 36 import android.os.AsyncTask; 37 import android.os.Bundle; 38 import android.os.Handler; 39 import android.util.Log; 40 import android.util.Range; 41 import android.view.OrientationEventListener; 42 import android.view.SurfaceHolder; 43 import android.view.SurfaceView; 44 import android.view.View; 45 import android.view.ViewGroup.LayoutParams; 46 import android.view.WindowManager; 47 import android.widget.AdapterView; 48 import android.widget.AdapterView.OnItemSelectedListener; 49 import android.widget.ArrayAdapter; 50 import android.widget.Button; 51 import android.widget.CompoundButton; 52 import android.widget.CheckBox; 53 import android.widget.ImageView; 54 import android.widget.RadioGroup; 55 import android.widget.SeekBar; 56 import android.widget.SeekBar.OnSeekBarChangeListener; 57 import android.widget.Spinner; 58 import android.widget.TextView; 59 import android.widget.ToggleButton; 60 61 import java.nio.ByteBuffer; 62 import java.util.ArrayList; 63 import java.util.Arrays; 64 import java.util.HashSet; 65 import java.util.List; 66 import java.util.Set; 67 68 import com.android.testingcamera2.R; 69 70 public class TestingCamera2 extends Activity implements SurfaceHolder.Callback { 71 72 private static final String TAG = "TestingCamera2"; 73 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 74 private CameraOps mCameraOps; 75 private String[] mAvailableCameraIds; 76 private String mCameraId; 77 private int mCaptureFormat = ImageFormat.JPEG; 78 private static final int mSeekBarMax = 100; 79 private static final long MAX_EXPOSURE = 200000000L; // 200ms 80 private static final long MIN_EXPOSURE = 100000L; // 100us 81 private static final long MAX_FRAME_DURATION = 1000000000L; // 1s 82 // Manual control change step size 83 private static final int STEP_SIZE = 100; 84 // Min and max sensitivity ISO values 85 private static final int MIN_SENSITIVITY = 100; 86 private static final int MAX_SENSITIVITY = 1600; 87 private static final int ORIENTATION_UNINITIALIZED = -1; 88 89 private int mLastOrientation = ORIENTATION_UNINITIALIZED; 90 private OrientationEventListener mOrientationEventListener; 91 private SurfaceView mPreviewView; 92 private SurfaceView mPreviewView2; 93 private ImageView mStillView; 94 95 private SurfaceHolder mCurrentPreviewHolder = null; 96 private SurfaceHolder mCurrentPreviewHolder2 = null; 97 98 private Spinner mCameraIdSpinner; 99 private Spinner mCaptureFormatSpinner; 100 private OnItemSelectedListener mCameraIdSelectionListener; 101 private OnItemSelectedListener mCaptureFormatSelectionListener; 102 private Button mInfoButton; 103 private Button mFlushButton; 104 private ToggleButton mFocusLockToggle; 105 private Spinner mFocusModeSpinner; 106 private CheckBox mUseMediaCodecCheckBox; 107 108 private SeekBar mSensitivityBar; 109 private SeekBar mExposureBar; 110 private SeekBar mFrameDurationBar; 111 112 private TextView mSensitivityInfoView; 113 private TextView mExposureInfoView; 114 private TextView mFrameDurationInfoView; 115 private TextView mCaptureResultView; 116 private ToggleButton mRecordingToggle; 117 private ToggleButton mManualCtrlToggle; 118 119 private CameraControls mCameraControl = null; 120 private final Set<View> mManualControls = new HashSet<View>(); 121 private final Set<View> mAutoControls = new HashSet<View>(); 122 123 private static final int PERMISSIONS_REQUEST_CAMERA = 1; 124 125 Handler mMainHandler; 126 boolean mUseMediaCodec; 127 128 @Override onCreate(Bundle savedInstanceState)129 public void onCreate(Bundle savedInstanceState) { 130 super.onCreate(savedInstanceState); 131 132 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 133 WindowManager.LayoutParams.FLAG_FULLSCREEN); 134 135 setContentView(R.layout.main); 136 137 mPreviewView = (SurfaceView) findViewById(R.id.preview_view); 138 mPreviewView.getHolder().addCallback(this); 139 140 mPreviewView2 = (SurfaceView) findViewById(R.id.preview_view2); 141 mPreviewView2.getHolder().addCallback(this); 142 143 mStillView = (ImageView) findViewById(R.id.still_view); 144 145 mCameraIdSpinner = (Spinner) findViewById(R.id.camera_id_spinner); 146 mCaptureFormatSpinner = (Spinner) findViewById(R.id.still_format_spinner); 147 mInfoButton = (Button) findViewById(R.id.info_button); 148 mInfoButton.setOnClickListener(mInfoButtonListener); 149 mFlushButton = (Button) findViewById(R.id.flush_button); 150 mFlushButton.setOnClickListener(mFlushButtonListener); 151 152 mFocusLockToggle = (ToggleButton) findViewById(R.id.focus_button); 153 mFocusLockToggle.setOnClickListener(mFocusLockToggleListener); 154 mFocusModeSpinner = (Spinner) findViewById(R.id.focus_mode_spinner); 155 mAutoControls.add(mFocusLockToggle); 156 157 mRecordingToggle = (ToggleButton) findViewById(R.id.start_recording); 158 mRecordingToggle.setOnClickListener(mRecordingToggleListener); 159 mUseMediaCodecCheckBox = (CheckBox) findViewById(R.id.use_media_codec); 160 mUseMediaCodecCheckBox.setOnCheckedChangeListener(mUseMediaCodecListener); 161 mUseMediaCodecCheckBox.setChecked(mUseMediaCodec); 162 163 mManualCtrlToggle = (ToggleButton) findViewById(R.id.manual_control); 164 mManualCtrlToggle.setOnClickListener(mControlToggleListener); 165 166 mSensitivityBar = (SeekBar) findViewById(R.id.sensitivity_seekbar); 167 mSensitivityBar.setOnSeekBarChangeListener(mSensitivitySeekBarListener); 168 mSensitivityBar.setMax(mSeekBarMax); 169 mManualControls.add(mSensitivityBar); 170 171 mExposureBar = (SeekBar) findViewById(R.id.exposure_time_seekbar); 172 mExposureBar.setOnSeekBarChangeListener(mExposureSeekBarListener); 173 mExposureBar.setMax(mSeekBarMax); 174 mManualControls.add(mExposureBar); 175 176 mFrameDurationBar = (SeekBar) findViewById(R.id.frame_duration_seekbar); 177 mFrameDurationBar.setOnSeekBarChangeListener(mFrameDurationSeekBarListener); 178 mFrameDurationBar.setMax(mSeekBarMax); 179 mManualControls.add(mFrameDurationBar); 180 181 mSensitivityInfoView = (TextView) findViewById(R.id.sensitivity_bar_label); 182 mExposureInfoView = (TextView) findViewById(R.id.exposure_time_bar_label); 183 mFrameDurationInfoView = (TextView) findViewById(R.id.frame_duration_bar_label); 184 mCaptureResultView = (TextView) findViewById(R.id.capture_result_info_label); 185 186 enableManualControls(false); 187 mCameraControl = new CameraControls(); 188 189 // Get UI handler 190 mMainHandler = new Handler(); 191 192 try { 193 mCameraOps = CameraOps.create(this, mCameraOpsListener, mMainHandler); 194 } catch(ApiFailureException e) { 195 logException("Cannot create camera ops!",e); 196 } 197 198 // Map available camera Ids to camera id spinner dropdown list of strings 199 try { 200 mAvailableCameraIds = mCameraOps.getDevices(); 201 if (mAvailableCameraIds == null || mAvailableCameraIds.length == 0) { 202 throw new ApiFailureException("no devices"); 203 } 204 } catch(ApiFailureException e) { 205 logException("CameraOps::getDevices failed!",e); 206 } 207 mCameraId = mAvailableCameraIds[0]; 208 ArrayAdapter<String> cameraIdDataAdapter = new ArrayAdapter<>(TestingCamera2.this, 209 android.R.layout.simple_spinner_item, mAvailableCameraIds); 210 mCameraIdSpinner.setAdapter(cameraIdDataAdapter); 211 212 /* 213 * Change the camera ID and update preview when camera ID spinner's selected item changes 214 */ 215 mCameraIdSpinner.setOnItemSelectedListener(mCameraIdSelectedListener); 216 217 mOrientationEventListener = new OrientationEventListener(this) { 218 @Override 219 public void onOrientationChanged(int orientation) { 220 if (orientation == ORIENTATION_UNKNOWN) { 221 orientation = 0; 222 } 223 mCameraOps.updateOrientation(orientation); 224 } 225 }; 226 mOrientationEventListener.enable(); 227 // Process the initial configuration (for i.e. initial orientation) 228 // We need this because #onConfigurationChanged doesn't get called when the app launches 229 maybeUpdateConfiguration(getResources().getConfiguration()); 230 } 231 232 @Override onResume()233 public void onResume() { 234 super.onResume(); 235 if (VERBOSE) Log.v(TAG, String.format("onResume")); 236 237 if ((checkSelfPermission(Manifest.permission.CAMERA) 238 != PackageManager.PERMISSION_GRANTED ) 239 || (checkSelfPermission(Manifest.permission.RECORD_AUDIO) 240 != PackageManager.PERMISSION_GRANTED)) { 241 Log.i(TAG, "Requested camera/video permissions"); 242 requestPermissions(new String[] { 243 Manifest.permission.CAMERA, 244 Manifest.permission.RECORD_AUDIO }, 245 PERMISSIONS_REQUEST_CAMERA); 246 return; 247 } 248 249 setUpPreview(); 250 } 251 252 @Override onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)253 public void onRequestPermissionsResult(int requestCode, String[] permissions, 254 int[] grantResults) { 255 if (requestCode == PERMISSIONS_REQUEST_CAMERA) { 256 for (int i = 0; i < grantResults.length; i++) { 257 if (grantResults[i] == PackageManager.PERMISSION_DENIED) { 258 Log.i(TAG, "At least one permission denied, can't continue: " + permissions[i]); 259 finish(); 260 return; 261 } 262 } 263 264 Log.i(TAG, "All permissions granted"); 265 setUpPreview(); 266 } 267 } 268 setUpPreview()269 private void setUpPreview() { 270 try { 271 mCameraOps.minimalPreviewConfig(mCameraId, mPreviewView.getHolder(), 272 mPreviewView2.getHolder()); 273 mCurrentPreviewHolder = mPreviewView.getHolder(); 274 mCurrentPreviewHolder2 = mPreviewView2.getHolder(); 275 } catch (ApiFailureException e) { 276 logException("Can't configure preview surface: ",e); 277 } 278 } 279 280 @Override onPause()281 public void onPause() { 282 super.onPause(); 283 try { 284 if (VERBOSE) Log.v(TAG, String.format("onPause")); 285 286 mCameraOps.closeDevice(); 287 } catch (ApiFailureException e) { 288 logException("Can't close device: ",e); 289 } 290 mCurrentPreviewHolder = null; 291 mCurrentPreviewHolder2 = null; 292 } 293 294 @Override onDestroy()295 protected void onDestroy() { 296 mOrientationEventListener.disable(); 297 super.onDestroy(); 298 } 299 300 @Override onConfigurationChanged(Configuration newConfig)301 public void onConfigurationChanged(Configuration newConfig) { 302 super.onConfigurationChanged(newConfig); 303 304 if (VERBOSE) { 305 Log.v(TAG, String.format("onConfiguredChanged: orientation %x", 306 newConfig.orientation)); 307 } 308 309 maybeUpdateConfiguration(newConfig); 310 } 311 maybeUpdateConfiguration(Configuration newConfig)312 private void maybeUpdateConfiguration(Configuration newConfig) { 313 if (VERBOSE) { 314 Log.v(TAG, String.format("handleConfiguration: orientation %x", 315 newConfig.orientation)); 316 } 317 318 if (mLastOrientation != newConfig.orientation) { 319 mLastOrientation = newConfig.orientation; 320 updatePreviewOrientation(); 321 } 322 } 323 updatePreviewOrientation()324 private void updatePreviewOrientation() { 325 LayoutParams params = mPreviewView.getLayoutParams(); 326 LayoutParams params2 = mPreviewView2.getLayoutParams(); 327 int width = params.width; 328 int height = params.height; 329 330 if (VERBOSE) { 331 Log.v(TAG, String.format( 332 "onConfiguredChanged: current layout is %dx%d", width, 333 height)); 334 } 335 /** 336 * Force wide aspect ratios for landscape 337 * Force narrow aspect ratios for portrait 338 */ 339 if (mLastOrientation == Configuration.ORIENTATION_LANDSCAPE) { 340 if (height > width) { 341 int tmp = width; 342 width = height; 343 height = tmp; 344 } 345 } else if (mLastOrientation == Configuration.ORIENTATION_PORTRAIT) { 346 if (width > height) { 347 int tmp = width; 348 width = height; 349 height = tmp; 350 } 351 } 352 353 if (width != params.width && height != params.height) { 354 if (VERBOSE) { 355 Log.v(TAG, String.format( 356 "onConfiguredChanged: updating preview size to %dx%d", width, 357 height)); 358 } 359 params.width = width; 360 params.height = height; 361 mPreviewView.setLayoutParams(params); 362 363 params2.width = width; 364 params2.height = height; 365 mPreviewView2.setLayoutParams(params2); 366 } 367 } 368 369 /** SurfaceHolder.Callback methods */ 370 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)371 public void surfaceChanged(SurfaceHolder holder, 372 int format, 373 int width, 374 int height) { 375 if (VERBOSE) { 376 Log.v(TAG, String.format("surfaceChanged: format %x, width %d, height %d", format, 377 width, height)); 378 } 379 if ((mCurrentPreviewHolder != null && holder == mCurrentPreviewHolder) || 380 (mCurrentPreviewHolder2 != null && holder == mCurrentPreviewHolder2)) { 381 try { 382 mCameraOps.minimalPreview(mCurrentPreviewHolder, mCurrentPreviewHolder2, 383 mCameraControl); 384 } catch (ApiFailureException e) { 385 logException("Can't start minimal preview: ", e); 386 } 387 } 388 } 389 390 @Override surfaceCreated(SurfaceHolder holder)391 public void surfaceCreated(SurfaceHolder holder) { 392 393 } 394 395 @Override surfaceDestroyed(SurfaceHolder holder)396 public void surfaceDestroyed(SurfaceHolder holder) { 397 } 398 399 private final Button.OnClickListener mInfoButtonListener = new Button.OnClickListener() { 400 @Override 401 public void onClick(View v) { 402 final Handler uiHandler = new Handler(); 403 AsyncTask.execute(new Runnable() { 404 @Override 405 public void run() { 406 try { 407 mCameraOps.minimalStillCapture(mCaptureCallback, mCaptureResultListener, 408 uiHandler, mCameraControl, mCaptureFormat); 409 if (mCurrentPreviewHolder != null && mCurrentPreviewHolder2 != null) { 410 mCameraOps.minimalPreview(mCurrentPreviewHolder, 411 mCurrentPreviewHolder2, mCameraControl); 412 } 413 } catch (ApiFailureException e) { 414 logException("Can't take a still capture! ", e); 415 } 416 } 417 }); 418 } 419 }; 420 421 private final Button.OnClickListener mFlushButtonListener = new Button.OnClickListener() { 422 @Override 423 public void onClick(View v) { 424 AsyncTask.execute(new Runnable() { 425 @Override 426 public void run() { 427 try { 428 mCameraOps.flush(); 429 } catch (ApiFailureException e) { 430 logException("Can't flush!", e); 431 } 432 } 433 }); 434 } 435 }; 436 437 /** 438 * UI controls enable/disable for all manual controls 439 */ enableManualControls(boolean enabled)440 private void enableManualControls(boolean enabled) { 441 for (View v : mManualControls) { 442 v.setEnabled(enabled); 443 } 444 445 for (View v : mAutoControls) { 446 v.setEnabled(!enabled); 447 } 448 } 449 450 private final CameraOps.CaptureCallback mCaptureCallback = new CameraOps.CaptureCallback() { 451 @Override 452 public void onCaptureAvailable(Image capture) { 453 if (capture.getFormat() != ImageFormat.JPEG && 454 capture.getFormat() != ImageFormat.HEIC) { 455 Log.e(TAG, "Unexpected format: " + capture.getFormat()); 456 return; 457 } 458 ByteBuffer encodedBuffer = capture.getPlanes()[0].getBuffer(); 459 byte[] encodedData = new byte[encodedBuffer.capacity()]; 460 encodedBuffer.get(encodedData); 461 462 Bitmap b = BitmapFactory.decodeByteArray(encodedData, 0, encodedData.length); 463 mStillView.setImageBitmap(b); 464 } 465 }; 466 467 // TODO: this callback is not called for each capture, need figure out why. 468 private final CameraOps.CaptureResultListener mCaptureResultListener = 469 new CameraOps.CaptureResultListener() { 470 471 @Override 472 public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, 473 long timestamp, long frameNumber) { 474 } 475 476 @Override 477 public void onCaptureCompleted( 478 CameraCaptureSession session, CaptureRequest request, 479 TotalCaptureResult result) { 480 Log.i(TAG, "Capture result is available"); 481 Integer reqCtrlMode; 482 Integer resCtrlMode; 483 if (request == null || result ==null) { 484 Log.e(TAG, "request/result is invalid"); 485 return; 486 } 487 Log.i(TAG, "Capture complete"); 488 final StringBuffer info = new StringBuffer("Capture Result:\n"); 489 490 reqCtrlMode = request.get(CaptureRequest.CONTROL_MODE); 491 resCtrlMode = result.get(CaptureResult.CONTROL_MODE); 492 info.append("Control mode: request " + reqCtrlMode + ". result " + resCtrlMode); 493 info.append("\n"); 494 495 Integer reqSen = request.get(CaptureRequest.SENSOR_SENSITIVITY); 496 Integer resSen = result.get(CaptureResult.SENSOR_SENSITIVITY); 497 info.append("Sensitivity: request " + reqSen + ". result " + resSen); 498 info.append("\n"); 499 500 Long reqExp = request.get(CaptureRequest.SENSOR_EXPOSURE_TIME); 501 Long resExp = result.get(CaptureResult.SENSOR_EXPOSURE_TIME); 502 info.append("Exposure: request " + reqExp + ". result " + resExp); 503 info.append("\n"); 504 505 Long reqFD = request.get(CaptureRequest.SENSOR_FRAME_DURATION); 506 Long resFD = result.get(CaptureResult.SENSOR_FRAME_DURATION); 507 info.append("Frame duration: request " + reqFD + ". result " + resFD); 508 info.append("\n"); 509 510 List<CaptureResult.Key<?>> resultKeys = result.getKeys(); 511 if (VERBOSE) { 512 CaptureResult.Key<?>[] arrayKeys = 513 resultKeys.toArray(new CaptureResult.Key<?>[0]); 514 Log.v(TAG, "onCaptureCompleted - dumping keys: " + 515 Arrays.toString(arrayKeys)); 516 } 517 info.append("Total keys: " + resultKeys.size()); 518 info.append("\n"); 519 520 if (mMainHandler != null) { 521 mMainHandler.post (new Runnable() { 522 @Override 523 public void run() { 524 // Update UI for capture result 525 mCaptureResultView.setText(info); 526 } 527 }); 528 } 529 } 530 531 @Override 532 public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, 533 CaptureFailure failure) { 534 Log.e(TAG, "Capture failed"); 535 } 536 }; 537 logException(String msg, Throwable e)538 private void logException(String msg, Throwable e) { 539 Log.e(TAG, msg + Log.getStackTraceString(e)); 540 } 541 getRadioFmt()542 private RadioGroup getRadioFmt() { 543 return (RadioGroup)findViewById(R.id.radio_fmt); 544 } 545 getOutputFormat()546 private int getOutputFormat() { 547 RadioGroup fmt = getRadioFmt(); 548 switch (fmt.getCheckedRadioButtonId()) { 549 case R.id.radio_mp4: 550 return MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4; 551 552 case R.id.radio_webm: 553 return MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM; 554 555 default: 556 throw new IllegalStateException("Checked button unrecognized."); 557 } 558 } 559 560 private final OnSeekBarChangeListener mSensitivitySeekBarListener = 561 new OnSeekBarChangeListener() { 562 563 @Override 564 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 565 Range<Integer> defaultRange = new Range<Integer>(MIN_SENSITIVITY, 566 MAX_SENSITIVITY); 567 CameraCharacteristics properties = mCameraOps.getCameraCharacteristics(); 568 Range<Integer> sensitivityRange = properties.get( 569 CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE); 570 if (sensitivityRange == null || sensitivityRange.getLower() > MIN_SENSITIVITY || 571 sensitivityRange.getUpper() < MAX_SENSITIVITY) { 572 Log.e(TAG, "unable to get sensitivity range, use default range"); 573 sensitivityRange = defaultRange; 574 } 575 int min = sensitivityRange.getLower(); 576 int max = sensitivityRange.getUpper(); 577 float progressFactor = progress / (float)mSeekBarMax; 578 int curSensitivity = (int) (min + (max - min) * progressFactor); 579 curSensitivity = (curSensitivity / STEP_SIZE ) * STEP_SIZE; 580 mCameraControl.getManualControls().setSensitivity(curSensitivity); 581 // Update the sensitivity info 582 StringBuffer info = new StringBuffer("Sensitivity(ISO):"); 583 info.append("" + curSensitivity); 584 mSensitivityInfoView.setText(info); 585 mCameraOps.updatePreview(mCameraControl); 586 } 587 588 @Override 589 public void onStartTrackingTouch(SeekBar seekBar) { 590 } 591 592 @Override 593 public void onStopTrackingTouch(SeekBar seekBar) { 594 } 595 }; 596 597 private final OnSeekBarChangeListener mExposureSeekBarListener = 598 new OnSeekBarChangeListener() { 599 600 @Override 601 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 602 Range<Long> defaultRange = new Range<Long>(MIN_EXPOSURE, MAX_EXPOSURE); 603 CameraCharacteristics properties = mCameraOps.getCameraCharacteristics(); 604 Range<Long> exposureRange = properties.get( 605 CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE); 606 // Not enforce the max value check here, most of the devices don't support 607 // larger than 30s exposure time 608 if (exposureRange == null || exposureRange.getLower() > MIN_EXPOSURE || 609 exposureRange.getUpper() < 0) { 610 exposureRange = defaultRange; 611 Log.e(TAG, "exposure time range is invalid, use default range"); 612 } 613 long min = exposureRange.getLower(); 614 long max = exposureRange.getUpper(); 615 float progressFactor = progress / (float)mSeekBarMax; 616 long curExposureTime = (long) (min + (max - min) * progressFactor); 617 mCameraControl.getManualControls().setExposure(curExposureTime); 618 // Update the sensitivity info 619 StringBuffer info = new StringBuffer("Exposure Time:"); 620 info.append("" + curExposureTime / 1000000.0 + "ms"); 621 mExposureInfoView.setText(info); 622 mCameraOps.updatePreview(mCameraControl); 623 } 624 625 @Override 626 public void onStartTrackingTouch(SeekBar seekBar) { 627 } 628 629 @Override 630 public void onStopTrackingTouch(SeekBar seekBar) { 631 } 632 }; 633 634 private final OnSeekBarChangeListener mFrameDurationSeekBarListener = 635 new OnSeekBarChangeListener() { 636 637 @Override 638 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 639 CameraCharacteristics properties = mCameraOps.getCameraCharacteristics(); 640 Long frameDurationMax = properties.get( 641 CameraCharacteristics.SENSOR_INFO_MAX_FRAME_DURATION); 642 if (frameDurationMax == null || frameDurationMax <= 0) { 643 frameDurationMax = MAX_FRAME_DURATION; 644 Log.e(TAG, "max frame duration is invalid, set to " + frameDurationMax); 645 } 646 // Need calculate from different resolution, hard code to 10ms for now. 647 long min = 10000000L; 648 long max = frameDurationMax; 649 float progressFactor = progress / (float)mSeekBarMax; 650 long curFrameDuration = (long) (min + (max - min) * progressFactor); 651 mCameraControl.getManualControls().setFrameDuration(curFrameDuration); 652 // Update the sensitivity info 653 StringBuffer info = new StringBuffer("Frame Duration:"); 654 info.append("" + curFrameDuration / 1000000.0 + "ms"); 655 mFrameDurationInfoView.setText(info); 656 mCameraOps.updatePreview(mCameraControl); 657 } 658 659 @Override 660 public void onStartTrackingTouch(SeekBar seekBar) { 661 } 662 663 @Override 664 public void onStopTrackingTouch(SeekBar seekBar) { 665 } 666 }; 667 668 private final View.OnClickListener mControlToggleListener = 669 new View.OnClickListener() { 670 @Override 671 public void onClick(View v) { 672 boolean enableManual; 673 if (mManualCtrlToggle.isChecked()) { 674 enableManual = true; 675 } else { 676 enableManual = false; 677 } 678 mCameraControl.getManualControls().setManualControlEnabled(enableManual); 679 enableManualControls(enableManual); 680 mCameraOps.updatePreview(mCameraControl); 681 } 682 }; 683 684 private final View.OnClickListener mRecordingToggleListener = 685 new View.OnClickListener() { 686 @Override 687 public void onClick(View v) { 688 if (mRecordingToggle.isChecked()) { 689 try { 690 Log.i(TAG, "start recording, useMediaCodec = " + mUseMediaCodec); 691 RadioGroup fmt = getRadioFmt(); 692 fmt.setActivated(false); 693 mCameraOps.startRecording( 694 /* applicationContext */ TestingCamera2.this, 695 /* useMediaCodec */ mUseMediaCodec, 696 /* outputFormat */ getOutputFormat()); 697 } catch (ApiFailureException e) { 698 logException("Failed to start recording", e); 699 } 700 } else { 701 try { 702 mCameraOps.stopRecording(TestingCamera2.this); 703 getRadioFmt().setActivated(true); 704 } catch (ApiFailureException e) { 705 logException("Failed to stop recording", e); 706 } 707 } 708 } 709 }; 710 711 private final View.OnClickListener mFocusLockToggleListener = 712 new View.OnClickListener() { 713 @Override 714 public void onClick(View v) { 715 if (VERBOSE) { 716 Log.v(TAG, "focus_lock#onClick - start"); 717 } 718 719 CameraAutoFocusControls afControls = mCameraControl.getAfControls(); 720 721 if (mFocusLockToggle.isChecked()) { 722 Log.i(TAG, "lock focus"); 723 724 afControls.setPendingTriggerStart(); 725 } else { 726 Log.i(TAG, "unlock focus"); 727 728 afControls.setPendingTriggerCancel(); 729 } 730 731 mCameraOps.updatePreview(mCameraControl); 732 733 if (VERBOSE) { 734 Log.v(TAG, "focus_lock#onClick - end"); 735 } 736 } 737 }; 738 739 private final CompoundButton.OnCheckedChangeListener mUseMediaCodecListener = 740 new CompoundButton.OnCheckedChangeListener() { 741 @Override 742 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 743 mUseMediaCodec = isChecked; 744 } 745 }; 746 747 private final OnItemSelectedListener mCameraIdSelectedListener = 748 new OnItemSelectedListener() { 749 @Override 750 public void onItemSelected(AdapterView<?> parent, View view, int position, 751 long id) { 752 String cameraId = mAvailableCameraIds[position]; 753 754 if (cameraId != mCameraId) { 755 Log.i(TAG, "Switch to camera " + cameraId); 756 try { 757 mCameraOps.minimalPreviewConfig(cameraId, mPreviewView.getHolder(), 758 mPreviewView2.getHolder()); 759 760 if (mCurrentPreviewHolder != null && mCurrentPreviewHolder2 != null) { 761 mCameraOps.minimalPreview(mCurrentPreviewHolder, 762 mCurrentPreviewHolder2, mCameraControl); 763 } 764 } catch (ApiFailureException e) { 765 logException("Can't configure preview surface: ", e); 766 } 767 mCameraId = cameraId; 768 } 769 } 770 771 @Override 772 public void onNothingSelected(AdapterView<?> parent) { 773 // Do nothing 774 } 775 }; 776 777 private final CameraOps.Listener mCameraOpsListener = new CameraOps.Listener() { 778 @Override 779 public void onCameraOpened(String cameraId, CameraCharacteristics characteristics) { 780 /* 781 * Populate dynamic per-camera settings 782 */ 783 784 // Map available AF Modes -> AF mode spinner dropdown list of strings 785 int[] availableAfModes = 786 characteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); 787 788 String[] allAfModes = getResources().getStringArray(R.array.focus_mode_spinner_arrays); 789 790 final List<String> afModeList = new ArrayList<>(); 791 final int[] afModePositions = new int[availableAfModes.length]; 792 793 int i = 0; 794 for (int mode : availableAfModes) { 795 afModeList.add(allAfModes[mode]); 796 afModePositions[i++] = mode; 797 } 798 799 ArrayAdapter<String> dataAdapter = new ArrayAdapter<>(TestingCamera2.this, 800 android.R.layout.simple_spinner_item, afModeList); 801 dataAdapter.setDropDownViewResource( 802 android.R.layout.simple_spinner_dropdown_item); 803 mFocusModeSpinner.setAdapter(dataAdapter); 804 805 /* 806 * Change the AF mode and update preview when AF spinner's selected item changes 807 */ 808 mFocusModeSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { 809 810 @Override 811 public void onItemSelected(AdapterView<?> parent, View view, int position, 812 long id) { 813 int afMode = afModePositions[position]; 814 815 Log.i(TAG, "Change auto-focus mode to " + afModeList.get(position) 816 + " " + afMode); 817 818 mCameraControl.getAfControls().setAfMode(afMode); 819 820 mCameraOps.updatePreview(mCameraControl); 821 } 822 823 @Override 824 public void onNothingSelected(AdapterView<?> parent) { 825 // Do nothing 826 } 827 }); 828 829 // Map available still capture formats -> capture format spinner dropdown list of 830 // strings 831 StreamConfigurationMap streamConfigMap = 832 characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 833 final List<String> captureFormatList = new ArrayList<>(); 834 835 captureFormatList.add("JPEG"); 836 if (streamConfigMap.isOutputSupportedFor(ImageFormat.HEIC)) { 837 captureFormatList.add("HEIC"); 838 } 839 840 dataAdapter = new ArrayAdapter<>(TestingCamera2.this, 841 android.R.layout.simple_spinner_item, captureFormatList); 842 dataAdapter.setDropDownViewResource( 843 android.R.layout.simple_spinner_dropdown_item); 844 mCaptureFormatSpinner.setAdapter(dataAdapter); 845 846 /* 847 * Change the capture format and update preview when format spinner's selected item changes 848 */ 849 mCaptureFormatSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { 850 851 @Override 852 public void onItemSelected(AdapterView<?> parent, View view, int position, 853 long id) { 854 int format = (position == 0 ? ImageFormat.JPEG : ImageFormat.HEIC); 855 856 Log.i(TAG, "Change image capture format to " + captureFormatList.get(position) 857 + " " + format); 858 859 mCaptureFormat = format; 860 } 861 862 @Override 863 public void onNothingSelected(AdapterView<?> parent) { 864 // Do nothing 865 } 866 }); 867 } 868 }; 869 } 870