1 /* 2 * Copyright (C) 2012 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.testingcamera; 18 19 import android.annotation.SuppressLint; 20 import android.app.Activity; 21 import android.app.FragmentManager; 22 import android.content.res.Resources; 23 import android.graphics.ImageFormat; 24 import android.hardware.Camera; 25 import android.hardware.Camera.Parameters; 26 import android.hardware.Camera.ErrorCallback; 27 import android.media.CamcorderProfile; 28 import android.media.MediaRecorder; 29 import android.media.MediaScannerConnection; 30 import android.net.Uri; 31 import android.os.Bundle; 32 import android.os.Environment; 33 import android.os.Handler; 34 import android.os.SystemClock; 35 import android.view.View; 36 import android.view.Surface; 37 import android.view.SurfaceHolder; 38 import android.view.SurfaceView; 39 import android.view.View.OnClickListener; 40 import android.widget.AdapterView; 41 import android.widget.AdapterView.OnItemSelectedListener; 42 import android.widget.ArrayAdapter; 43 import android.widget.Button; 44 import android.widget.CheckBox; 45 import android.widget.LinearLayout; 46 import android.widget.LinearLayout.LayoutParams; 47 import android.widget.SeekBar; 48 import android.widget.Spinner; 49 import android.widget.TextView; 50 import android.widget.ToggleButton; 51 import android.renderscript.RenderScript; 52 import android.text.Layout; 53 import android.text.method.ScrollingMovementMethod; 54 import android.util.Log; 55 import android.util.SparseArray; 56 57 import java.io.File; 58 import java.io.IOException; 59 import java.io.PrintWriter; 60 import java.io.StringWriter; 61 import java.text.SimpleDateFormat; 62 import java.util.ArrayList; 63 import java.util.Date; 64 import java.util.HashSet; 65 import java.util.List; 66 import java.util.Set; 67 68 /** 69 * A simple test application for the camera API. 70 * 71 * The goal of this application is to allow all camera API features to be 72 * exercised, and all information provided by the API to be shown. 73 */ 74 public class TestingCamera extends Activity 75 implements SurfaceHolder.Callback, Camera.PreviewCallback, 76 Camera.ErrorCallback { 77 78 /** UI elements */ 79 private SurfaceView mPreviewView; 80 private SurfaceHolder mPreviewHolder; 81 private LinearLayout mPreviewColumn; 82 83 private SurfaceView mCallbackView; 84 private SurfaceHolder mCallbackHolder; 85 86 private Spinner mCameraSpinner; 87 private CheckBox mKeepOpenCheckBox; 88 private Button mInfoButton; 89 private Spinner mPreviewSizeSpinner; 90 private Spinner mPreviewFrameRateSpinner; 91 private ToggleButton mPreviewToggle; 92 private Spinner mAutofocusModeSpinner; 93 private Button mAutofocusButton; 94 private Button mCancelAutofocusButton; 95 private TextView mFlashModeSpinnerLabel; 96 private Spinner mFlashModeSpinner; 97 private ToggleButton mExposureLockToggle; 98 private Spinner mSnapshotSizeSpinner; 99 private Button mTakePictureButton; 100 private Spinner mCamcorderProfileSpinner; 101 private Spinner mVideoRecordSizeSpinner; 102 private Spinner mVideoFrameRateSpinner; 103 private ToggleButton mRecordToggle; 104 private CheckBox mRecordHandoffCheckBox; 105 private ToggleButton mRecordStabilizationToggle; 106 private ToggleButton mRecordHintToggle; 107 private ToggleButton mLockCameraToggle; 108 private Spinner mCallbackFormatSpinner; 109 private ToggleButton mCallbackToggle; 110 private TextView mColorEffectSpinnerLabel; 111 private Spinner mColorEffectSpinner; 112 private SeekBar mZoomSeekBar; 113 114 private TextView mLogView; 115 116 private Set<View> mOpenOnlyControls = new HashSet<View>(); 117 private Set<View> mPreviewOnlyControls = new HashSet<View>(); 118 119 private SparseArray<String> mFormatNames; 120 121 /** Camera state */ 122 private int mCameraId; 123 private Camera mCamera; 124 private Camera.Parameters mParams; 125 private List<Camera.Size> mPreviewSizes; 126 private int mPreviewSize = 0; 127 private List<Integer> mPreviewFrameRates; 128 private int mPreviewFrameRate = 0; 129 private List<Integer> mPreviewFormats; 130 private int mPreviewFormat = 0; 131 private List<String> mAfModes; 132 private int mAfMode = 0; 133 private List<String> mFlashModes; 134 private int mFlashMode = 0; 135 private List<Camera.Size> mSnapshotSizes; 136 private int mSnapshotSize = 0; 137 private List<CamcorderProfile> mCamcorderProfiles; 138 private int mCamcorderProfile = 0; 139 private List<Camera.Size> mVideoRecordSizes; 140 private int mVideoRecordSize = 0; 141 private List<Integer> mVideoFrameRates; 142 private int mVideoFrameRate = 0; 143 private List<String> mColorEffects; 144 private int mColorEffect = 0; 145 private int mZoom = 0; 146 147 private MediaRecorder mRecorder; 148 private File mRecordingFile; 149 150 private RenderScript mRS; 151 152 private boolean mCallbacksEnabled = false; 153 private CallbackProcessor mCallbackProcessor = null; 154 long mLastCallbackTimestamp = -1; 155 float mCallbackAvgFrameDuration = 30; 156 int mCallbackFrameCount = 0; 157 private static final float MEAN_FPS_HISTORY_COEFF = 0.9f; 158 private static final float MEAN_FPS_MEASUREMENT_COEFF = 0.1f; 159 private static final int FPS_REPORTING_PERIOD = 200; // frames 160 private static final int CALLBACK_BUFFER_COUNT = 3; 161 162 private static final int CAMERA_UNINITIALIZED = 0; 163 private static final int CAMERA_OPEN = 1; 164 private static final int CAMERA_PREVIEW = 2; 165 private static final int CAMERA_TAKE_PICTURE = 3; 166 private static final int CAMERA_RECORD = 4; 167 private int mState = CAMERA_UNINITIALIZED; 168 169 private static final int NO_CAMERA_ID = -1; 170 171 /** Misc variables */ 172 173 private static final String TAG = "TestingCamera"; 174 175 176 /** Activity lifecycle */ 177 178 @Override onCreate(Bundle savedInstanceState)179 public void onCreate(Bundle savedInstanceState) { 180 super.onCreate(savedInstanceState); 181 182 setContentView(R.layout.main); 183 184 mPreviewColumn = (LinearLayout) findViewById(R.id.preview_column); 185 186 mPreviewView = (SurfaceView) findViewById(R.id.preview); 187 mPreviewView.getHolder().addCallback(this); 188 189 mCallbackView = (SurfaceView)findViewById(R.id.callback_view); 190 191 mCameraSpinner = (Spinner) findViewById(R.id.camera_spinner); 192 mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener); 193 194 mKeepOpenCheckBox = (CheckBox) findViewById(R.id.keep_open_checkbox); 195 196 mInfoButton = (Button) findViewById(R.id.info_button); 197 mInfoButton.setOnClickListener(mInfoButtonListener); 198 mOpenOnlyControls.add(mInfoButton); 199 200 mPreviewSizeSpinner = (Spinner) findViewById(R.id.preview_size_spinner); 201 mPreviewSizeSpinner.setOnItemSelectedListener(mPreviewSizeListener); 202 mOpenOnlyControls.add(mPreviewSizeSpinner); 203 204 mPreviewFrameRateSpinner = (Spinner) findViewById(R.id.preview_frame_rate_spinner); 205 mPreviewFrameRateSpinner.setOnItemSelectedListener(mPreviewFrameRateListener); 206 mOpenOnlyControls.add(mPreviewFrameRateSpinner); 207 208 mPreviewToggle = (ToggleButton) findViewById(R.id.start_preview); 209 mPreviewToggle.setOnClickListener(mPreviewToggleListener); 210 mOpenOnlyControls.add(mPreviewToggle); 211 212 mAutofocusModeSpinner = (Spinner) findViewById(R.id.af_mode_spinner); 213 mAutofocusModeSpinner.setOnItemSelectedListener(mAutofocusModeListener); 214 mOpenOnlyControls.add(mAutofocusModeSpinner); 215 216 mAutofocusButton = (Button) findViewById(R.id.af_button); 217 mAutofocusButton.setOnClickListener(mAutofocusButtonListener); 218 mPreviewOnlyControls.add(mAutofocusButton); 219 220 mCancelAutofocusButton = (Button) findViewById(R.id.af_cancel_button); 221 mCancelAutofocusButton.setOnClickListener(mCancelAutofocusButtonListener); 222 mPreviewOnlyControls.add(mCancelAutofocusButton); 223 224 mFlashModeSpinnerLabel = (TextView) findViewById(R.id.flash_mode_spinner_label); 225 226 mFlashModeSpinner = (Spinner) findViewById(R.id.flash_mode_spinner); 227 mFlashModeSpinner.setOnItemSelectedListener(mFlashModeListener); 228 mOpenOnlyControls.add(mFlashModeSpinner); 229 230 mExposureLockToggle = (ToggleButton) findViewById(R.id.exposure_lock); 231 mExposureLockToggle.setOnClickListener(mExposureLockToggleListener); 232 mOpenOnlyControls.add(mExposureLockToggle); 233 234 mZoomSeekBar = (SeekBar) findViewById(R.id.zoom_seekbar); 235 mZoomSeekBar.setOnSeekBarChangeListener(mZoomSeekBarListener); 236 237 mSnapshotSizeSpinner = (Spinner) findViewById(R.id.snapshot_size_spinner); 238 mSnapshotSizeSpinner.setOnItemSelectedListener(mSnapshotSizeListener); 239 mOpenOnlyControls.add(mSnapshotSizeSpinner); 240 241 mTakePictureButton = (Button) findViewById(R.id.take_picture); 242 mTakePictureButton.setOnClickListener(mTakePictureListener); 243 mPreviewOnlyControls.add(mTakePictureButton); 244 245 mCamcorderProfileSpinner = (Spinner) findViewById(R.id.camcorder_profile_spinner); 246 mCamcorderProfileSpinner.setOnItemSelectedListener(mCamcorderProfileListener); 247 mOpenOnlyControls.add(mCamcorderProfileSpinner); 248 249 mVideoRecordSizeSpinner = (Spinner) findViewById(R.id.video_record_size_spinner); 250 mVideoRecordSizeSpinner.setOnItemSelectedListener(mVideoRecordSizeListener); 251 mOpenOnlyControls.add(mVideoRecordSizeSpinner); 252 253 mVideoFrameRateSpinner = (Spinner) findViewById(R.id.video_frame_rate_spinner); 254 mVideoFrameRateSpinner.setOnItemSelectedListener(mVideoFrameRateListener); 255 mOpenOnlyControls.add(mVideoFrameRateSpinner); 256 257 mRecordToggle = (ToggleButton) findViewById(R.id.start_record); 258 mRecordToggle.setOnClickListener(mRecordToggleListener); 259 mPreviewOnlyControls.add(mRecordToggle); 260 261 mRecordHandoffCheckBox = (CheckBox) findViewById(R.id.record_handoff_checkbox); 262 263 mRecordStabilizationToggle = (ToggleButton) findViewById(R.id.record_stabilization); 264 mRecordStabilizationToggle.setOnClickListener(mRecordStabilizationToggleListener); 265 mOpenOnlyControls.add(mRecordStabilizationToggle); 266 267 mRecordHintToggle = (ToggleButton) findViewById(R.id.record_hint); 268 mRecordHintToggle.setOnClickListener(mRecordHintToggleListener); 269 mOpenOnlyControls.add(mRecordHintToggle); 270 271 mLockCameraToggle = (ToggleButton) findViewById(R.id.lock_camera); 272 mLockCameraToggle.setOnClickListener(mLockCameraToggleListener); 273 mLockCameraToggle.setChecked(true); // ON by default 274 mOpenOnlyControls.add(mLockCameraToggle); 275 276 mCallbackFormatSpinner = (Spinner) findViewById(R.id.callback_format_spinner); 277 mCallbackFormatSpinner.setOnItemSelectedListener(mCallbackFormatListener); 278 mOpenOnlyControls.add(mCallbackFormatSpinner); 279 280 mCallbackToggle = (ToggleButton) findViewById(R.id.enable_callbacks); 281 mCallbackToggle.setOnClickListener(mCallbackToggleListener); 282 mOpenOnlyControls.add(mCallbackToggle); 283 284 mColorEffectSpinnerLabel = (TextView) findViewById(R.id.color_effect_spinner_label); 285 286 mColorEffectSpinner = (Spinner) findViewById(R.id.color_effect_spinner); 287 mColorEffectSpinner.setOnItemSelectedListener(mColorEffectListener); 288 mOpenOnlyControls.add(mColorEffectSpinner); 289 290 mLogView = (TextView) findViewById(R.id.log); 291 mLogView.setMovementMethod(new ScrollingMovementMethod()); 292 293 mOpenOnlyControls.addAll(mPreviewOnlyControls); 294 295 mFormatNames = new SparseArray<String>(7); 296 mFormatNames.append(ImageFormat.JPEG, "JPEG"); 297 mFormatNames.append(ImageFormat.NV16, "NV16"); 298 mFormatNames.append(ImageFormat.NV21, "NV21"); 299 mFormatNames.append(ImageFormat.RGB_565, "RGB_565"); 300 mFormatNames.append(ImageFormat.UNKNOWN, "UNKNOWN"); 301 mFormatNames.append(ImageFormat.YUY2, "YUY2"); 302 mFormatNames.append(ImageFormat.YV12, "YV12"); 303 304 int numCameras = Camera.getNumberOfCameras(); 305 String[] cameraNames = new String[numCameras + 1]; 306 cameraNames[0] = "None"; 307 for (int i = 0; i < numCameras; i++) { 308 cameraNames[i + 1] = "Camera " + i; 309 } 310 311 mCameraSpinner.setAdapter( 312 new ArrayAdapter<String>(this, 313 R.layout.spinner_item, cameraNames)); 314 if (numCameras > 0) { 315 mCameraId = 0; 316 mCameraSpinner.setSelection(mCameraId + 1); 317 } else { 318 resetCamera(); 319 mCameraSpinner.setSelection(0); 320 } 321 322 mRS = RenderScript.create(this); 323 } 324 325 @Override onResume()326 public void onResume() { 327 super.onResume(); 328 log("onResume: Setting up"); 329 mPreviewHolder = null; 330 setUpCamera(); 331 } 332 333 @Override onPause()334 public void onPause() { 335 super.onPause(); 336 if (mState == CAMERA_RECORD) { 337 stopRecording(false); 338 } 339 if (mKeepOpenCheckBox.isChecked()) { 340 log("onPause: Not releasing camera"); 341 342 if (mState == CAMERA_PREVIEW) { 343 mCamera.stopPreview(); 344 mState = CAMERA_OPEN; 345 } 346 } else { 347 log("onPause: Releasing camera"); 348 349 if (mCamera != null) { 350 mCamera.release(); 351 } 352 mState = CAMERA_UNINITIALIZED; 353 } 354 } 355 356 /** SurfaceHolder.Callback methods */ 357 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)358 public void surfaceChanged(SurfaceHolder holder, 359 int format, 360 int width, 361 int height) { 362 if (holder == mPreviewView.getHolder()) { 363 if (mState >= CAMERA_OPEN) { 364 final int previewWidth = 365 mPreviewSizes.get(mPreviewSize).width; 366 final int previewHeight = 367 mPreviewSizes.get(mPreviewSize).height; 368 369 if ( Math.abs((float)previewWidth / previewHeight - 370 (float)width/height) > 0.01f) { 371 Handler h = new Handler(); 372 h.post(new Runnable() { 373 @Override 374 public void run() { 375 layoutPreview(); 376 } 377 }); 378 } 379 } 380 381 if (mPreviewHolder != null) { 382 return; 383 } 384 log("Surface holder available: " + width + " x " + height); 385 mPreviewHolder = holder; 386 try { 387 if (mCamera != null) { 388 mCamera.setPreviewDisplay(holder); 389 } 390 } catch (IOException e) { 391 logE("Unable to set up preview!"); 392 } 393 } else if (holder == mCallbackView.getHolder()) { 394 mCallbackHolder = holder; 395 } 396 } 397 398 @Override surfaceCreated(SurfaceHolder holder)399 public void surfaceCreated(SurfaceHolder holder) { 400 401 } 402 403 @Override surfaceDestroyed(SurfaceHolder holder)404 public void surfaceDestroyed(SurfaceHolder holder) { 405 mPreviewHolder = null; 406 } 407 setCameraDisplayOrientation()408 public void setCameraDisplayOrientation() { 409 android.hardware.Camera.CameraInfo info = 410 new android.hardware.Camera.CameraInfo(); 411 android.hardware.Camera.getCameraInfo(mCameraId, info); 412 int rotation = getWindowManager().getDefaultDisplay() 413 .getRotation(); 414 int degrees = 0; 415 switch (rotation) { 416 case Surface.ROTATION_0: degrees = 0; break; 417 case Surface.ROTATION_90: degrees = 90; break; 418 case Surface.ROTATION_180: degrees = 180; break; 419 case Surface.ROTATION_270: degrees = 270; break; 420 } 421 422 int result; 423 if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 424 result = (info.orientation + degrees) % 360; 425 result = (360 - result) % 360; // compensate the mirror 426 } else { // back-facing 427 result = (info.orientation - degrees + 360) % 360; 428 } 429 log(String.format( 430 "Camera sensor orientation %d, UI rotation %d, facing %s. Final orientation %d", 431 info.orientation, rotation, 432 info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT ? "FRONT" : "BACK", 433 result)); 434 mCamera.setDisplayOrientation(result); 435 } 436 437 /** UI controls enable/disable for all open-only controls */ enableOpenOnlyControls(boolean enabled)438 private void enableOpenOnlyControls(boolean enabled) { 439 for (View v : mOpenOnlyControls) { 440 v.setEnabled(enabled); 441 } 442 } 443 444 /** UI controls enable/disable for all preview-only controls */ enablePreviewOnlyControls(boolean enabled)445 private void enablePreviewOnlyControls(boolean enabled) { 446 for (View v : mPreviewOnlyControls) { 447 v.setEnabled(enabled); 448 } 449 } 450 451 /** UI listeners */ 452 453 private AdapterView.OnItemSelectedListener mCameraSpinnerListener = 454 new AdapterView.OnItemSelectedListener() { 455 @Override 456 public void onItemSelected(AdapterView<?> parent, 457 View view, int pos, long id) { 458 int cameraId = pos - 1; 459 if (mCameraId != cameraId) { 460 resetCamera(); 461 mCameraId = cameraId; 462 mPreviewToggle.setChecked(false); 463 setUpCamera(); 464 } 465 } 466 467 @Override 468 public void onNothingSelected(AdapterView<?> parent) { 469 470 } 471 }; 472 473 private OnClickListener mInfoButtonListener = new OnClickListener() { 474 @Override 475 public void onClick(View v) { 476 if (mCameraId != NO_CAMERA_ID) { 477 FragmentManager fm = getFragmentManager(); 478 InfoDialogFragment infoDialog = new InfoDialogFragment(); 479 infoDialog.updateInfo(mCameraId, mCamera); 480 infoDialog.show(fm, "info_dialog_fragment"); 481 } 482 } 483 }; 484 485 private AdapterView.OnItemSelectedListener mPreviewSizeListener = 486 new AdapterView.OnItemSelectedListener() { 487 @Override 488 public void onItemSelected(AdapterView<?> parent, 489 View view, int pos, long id) { 490 if (pos == mPreviewSize) return; 491 if (mState == CAMERA_PREVIEW) { 492 log("Stopping preview and callbacks to switch resolutions"); 493 stopCallbacks(); 494 mCamera.stopPreview(); 495 } 496 497 mPreviewSize = pos; 498 int width = mPreviewSizes.get(mPreviewSize).width; 499 int height = mPreviewSizes.get(mPreviewSize).height; 500 mParams.setPreviewSize(width, height); 501 502 log("Setting preview size to " + width + "x" + height); 503 504 mCamera.setParameters(mParams); 505 resizePreview(); 506 507 if (mState == CAMERA_PREVIEW) { 508 log("Restarting preview"); 509 mCamera.startPreview(); 510 } 511 } 512 513 @Override 514 public void onNothingSelected(AdapterView<?> parent) { 515 516 } 517 }; 518 519 private AdapterView.OnItemSelectedListener mPreviewFrameRateListener = 520 new AdapterView.OnItemSelectedListener() { 521 @Override 522 public void onItemSelected(AdapterView<?> parent, 523 View view, int pos, long id) { 524 if (pos == mPreviewFrameRate) return; 525 mPreviewFrameRate = pos; 526 mParams.setPreviewFrameRate(mPreviewFrameRates.get(mPreviewFrameRate)); 527 528 log("Setting preview frame rate to " + ((TextView)view).getText()); 529 530 mCamera.setParameters(mParams); 531 } 532 533 @Override 534 public void onNothingSelected(AdapterView<?> parent) { 535 536 } 537 }; 538 539 private View.OnClickListener mPreviewToggleListener = 540 new View.OnClickListener() { 541 @Override 542 public void onClick(View v) { 543 if (mState == CAMERA_TAKE_PICTURE) { 544 logE("Can't change preview state while taking picture!"); 545 return; 546 } 547 if (mPreviewToggle.isChecked()) { 548 log("Starting preview"); 549 mCamera.startPreview(); 550 mState = CAMERA_PREVIEW; 551 enablePreviewOnlyControls(true); 552 } else { 553 log("Stopping preview"); 554 mCamera.stopPreview(); 555 mState = CAMERA_OPEN; 556 557 enablePreviewOnlyControls(false); 558 } 559 } 560 }; 561 562 private OnItemSelectedListener mAutofocusModeListener = 563 new OnItemSelectedListener() { 564 @Override 565 public void onItemSelected(AdapterView<?> parent, 566 View view, int pos, long id) { 567 if (pos == mAfMode) return; 568 569 mAfMode = pos; 570 String focusMode = mAfModes.get(mAfMode); 571 log("Setting focus mode to " + focusMode); 572 if (focusMode == Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE || 573 focusMode == Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO) { 574 mCamera.setAutoFocusMoveCallback(mAutofocusMoveCallback); 575 } 576 mParams.setFocusMode(focusMode); 577 578 mCamera.setParameters(mParams); 579 } 580 581 @Override 582 public void onNothingSelected(AdapterView<?> arg0) { 583 584 } 585 }; 586 587 private OnClickListener mAutofocusButtonListener = 588 new View.OnClickListener() { 589 @Override 590 public void onClick(View v) { 591 log("Triggering autofocus"); 592 mCamera.autoFocus(mAutofocusCallback); 593 } 594 }; 595 596 private OnClickListener mCancelAutofocusButtonListener = 597 new View.OnClickListener() { 598 @Override 599 public void onClick(View v) { 600 log("Cancelling autofocus"); 601 mCamera.cancelAutoFocus(); 602 } 603 }; 604 605 private Camera.AutoFocusCallback mAutofocusCallback = 606 new Camera.AutoFocusCallback() { 607 @Override 608 public void onAutoFocus(boolean success, Camera camera) { 609 log("Autofocus completed: " + (success ? "success" : "failure") ); 610 } 611 }; 612 613 private Camera.AutoFocusMoveCallback mAutofocusMoveCallback = 614 new Camera.AutoFocusMoveCallback() { 615 @Override 616 public void onAutoFocusMoving(boolean start, Camera camera) { 617 log("Autofocus movement: " + (start ? "starting" : "stopped") ); 618 } 619 }; 620 621 private OnItemSelectedListener mFlashModeListener = 622 new OnItemSelectedListener() { 623 @Override 624 public void onItemSelected(AdapterView<?> parent, 625 View view, int pos, long id) { 626 if (pos == mFlashMode) return; 627 628 mFlashMode = pos; 629 String flashMode = mFlashModes.get(mFlashMode); 630 log("Setting flash mode to " + flashMode); 631 mParams.setFlashMode(flashMode); 632 mCamera.setParameters(mParams); 633 } 634 635 @Override 636 public void onNothingSelected(AdapterView<?> arg0) { 637 638 } 639 }; 640 641 642 private AdapterView.OnItemSelectedListener mSnapshotSizeListener = 643 new AdapterView.OnItemSelectedListener() { 644 @Override 645 public void onItemSelected(AdapterView<?> parent, 646 View view, int pos, long id) { 647 if (pos == mSnapshotSize) return; 648 649 mSnapshotSize = pos; 650 int width = mSnapshotSizes.get(mSnapshotSize).width; 651 int height = mSnapshotSizes.get(mSnapshotSize).height; 652 log("Setting snapshot size to " + width + " x " + height); 653 654 mParams.setPictureSize(width, height); 655 656 mCamera.setParameters(mParams); 657 } 658 659 @Override 660 public void onNothingSelected(AdapterView<?> parent) { 661 662 } 663 }; 664 665 private View.OnClickListener mTakePictureListener = 666 new View.OnClickListener() { 667 @Override 668 public void onClick(View v) { 669 log("Taking picture"); 670 if (mState == CAMERA_PREVIEW) { 671 mState = CAMERA_TAKE_PICTURE; 672 enablePreviewOnlyControls(false); 673 mPreviewToggle.setChecked(false); 674 675 mCamera.takePicture(mShutterCb, mRawCb, mPostviewCb, mJpegCb); 676 } else { 677 logE("Can't take picture while not running preview!"); 678 } 679 } 680 }; 681 682 private AdapterView.OnItemSelectedListener mCamcorderProfileListener = 683 new AdapterView.OnItemSelectedListener() { 684 @Override 685 public void onItemSelected(AdapterView<?> parent, 686 View view, int pos, long id) { 687 if (pos != mCamcorderProfile) { 688 log("Setting camcorder profile to " + ((TextView)view).getText()); 689 mCamcorderProfile = pos; 690 } 691 692 // Additionally change video recording size to match 693 mVideoRecordSize = 0; // "default", in case it's not found 694 int width = mCamcorderProfiles.get(pos).videoFrameWidth; 695 int height = mCamcorderProfiles.get(pos).videoFrameHeight; 696 for (int i = 0; i < mVideoRecordSizes.size(); i++) { 697 Camera.Size s = mVideoRecordSizes.get(i); 698 if (width == s.width && height == s.height) { 699 mVideoRecordSize = i; 700 break; 701 } 702 } 703 log("Setting video record size to " + mVideoRecordSize); 704 mVideoRecordSizeSpinner.setSelection(mVideoRecordSize); 705 } 706 707 @Override 708 public void onNothingSelected(AdapterView<?> parent) { 709 710 } 711 }; 712 713 private AdapterView.OnItemSelectedListener mVideoRecordSizeListener = 714 new AdapterView.OnItemSelectedListener() { 715 @Override 716 public void onItemSelected(AdapterView<?> parent, 717 View view, int pos, long id) { 718 if (pos == mVideoRecordSize) return; 719 720 log("Setting video record size to " + ((TextView)view).getText()); 721 mVideoRecordSize = pos; 722 } 723 724 @Override 725 public void onNothingSelected(AdapterView<?> parent) { 726 727 } 728 }; 729 730 private AdapterView.OnItemSelectedListener mVideoFrameRateListener = 731 new AdapterView.OnItemSelectedListener() { 732 @Override 733 public void onItemSelected(AdapterView<?> parent, 734 View view, int pos, long id) { 735 if (pos == mVideoFrameRate) return; 736 737 log("Setting video frame rate to " + ((TextView)view).getText()); 738 mVideoFrameRate = pos; 739 } 740 741 @Override 742 public void onNothingSelected(AdapterView<?> parent) { 743 744 } 745 }; 746 747 private View.OnClickListener mRecordToggleListener = 748 new View.OnClickListener() { 749 @Override 750 public void onClick(View v) { 751 if (!mLockCameraToggle.isChecked()) { 752 logE("Re-lock camera before recording"); 753 return; 754 } 755 756 mPreviewToggle.setEnabled(false); 757 if (mState == CAMERA_PREVIEW) { 758 startRecording(); 759 } else if (mState == CAMERA_RECORD) { 760 stopRecording(false); 761 } else { 762 logE("Can't toggle recording in current state!"); 763 } 764 mPreviewToggle.setEnabled(true); 765 } 766 }; 767 768 private View.OnClickListener mRecordStabilizationToggleListener = 769 new View.OnClickListener() { 770 @Override 771 public void onClick(View v) { 772 boolean on = ((ToggleButton) v).isChecked(); 773 mParams.setVideoStabilization(on); 774 775 mCamera.setParameters(mParams); 776 } 777 }; 778 779 private View.OnClickListener mRecordHintToggleListener = 780 new View.OnClickListener() { 781 @Override 782 public void onClick(View v) { 783 boolean on = ((ToggleButton) v).isChecked(); 784 mParams.setRecordingHint(on); 785 786 mCamera.setParameters(mParams); 787 } 788 }; 789 790 private View.OnClickListener mLockCameraToggleListener = 791 new View.OnClickListener() { 792 @Override 793 public void onClick(View v) { 794 795 if (mState == CAMERA_RECORD) { 796 logE("Stop recording before toggling lock"); 797 return; 798 } 799 800 boolean on = ((ToggleButton) v).isChecked(); 801 802 if (on) { 803 mCamera.lock(); 804 log("Locked camera"); 805 } else { 806 mCamera.unlock(); 807 log("Unlocked camera"); 808 } 809 } 810 }; 811 812 private Camera.ShutterCallback mShutterCb = new Camera.ShutterCallback() { 813 @Override 814 public void onShutter() { 815 log("Shutter callback received"); 816 } 817 }; 818 819 private Camera.PictureCallback mRawCb = new Camera.PictureCallback() { 820 @Override 821 public void onPictureTaken(byte[] data, Camera camera) { 822 log("Raw callback received"); 823 } 824 }; 825 826 private Camera.PictureCallback mPostviewCb = new Camera.PictureCallback() { 827 @Override 828 public void onPictureTaken(byte[] data, Camera camera) { 829 log("Postview callback received"); 830 } 831 }; 832 833 private Camera.PictureCallback mJpegCb = new Camera.PictureCallback() { 834 @Override 835 public void onPictureTaken(byte[] data, Camera camera) { 836 log("JPEG picture callback received"); 837 FragmentManager fm = getFragmentManager(); 838 SnapshotDialogFragment snapshotDialog = new SnapshotDialogFragment(); 839 840 snapshotDialog.updateImage(data); 841 snapshotDialog.show(fm, "snapshot_dialog_fragment"); 842 843 mPreviewToggle.setEnabled(true); 844 845 mState = CAMERA_OPEN; 846 } 847 }; 848 849 private AdapterView.OnItemSelectedListener mCallbackFormatListener = 850 new AdapterView.OnItemSelectedListener() { 851 public void onItemSelected(AdapterView<?> parent, 852 View view, int pos, long id) { 853 mPreviewFormat = pos; 854 855 log("Setting preview format to " + 856 mFormatNames.get(mPreviewFormats.get(mPreviewFormat))); 857 858 switch (mState) { 859 case CAMERA_UNINITIALIZED: 860 return; 861 case CAMERA_OPEN: 862 break; 863 case CAMERA_PREVIEW: 864 if (mCallbacksEnabled) { 865 log("Stopping preview and callbacks to switch formats"); 866 stopCallbacks(); 867 mCamera.stopPreview(); 868 } 869 break; 870 case CAMERA_RECORD: 871 logE("Can't update format while recording active"); 872 return; 873 } 874 875 mParams.setPreviewFormat(mPreviewFormats.get(mPreviewFormat)); 876 mCamera.setParameters(mParams); 877 878 if (mCallbacksEnabled) { 879 if (mState == CAMERA_PREVIEW) { 880 mCamera.startPreview(); 881 } 882 } 883 884 configureCallbacks(mCallbackView.getWidth(), mCallbackView.getHeight()); 885 } 886 887 public void onNothingSelected(AdapterView<?> parent) { 888 889 } 890 }; 891 892 private View.OnClickListener mCallbackToggleListener = 893 new View.OnClickListener() { 894 public void onClick(View v) { 895 if (mCallbacksEnabled) { 896 log("Disabling preview callbacks"); 897 stopCallbacks(); 898 mCallbacksEnabled = false; 899 resizePreview(); 900 mCallbackView.setVisibility(View.GONE); 901 902 } else { 903 log("Enabling preview callbacks"); 904 mCallbacksEnabled = true; 905 resizePreview(); 906 mCallbackView.setVisibility(View.VISIBLE); 907 } 908 } 909 }; 910 911 912 // Internal methods 913 setUpCamera()914 void setUpCamera() { 915 if (mCameraId == NO_CAMERA_ID) return; 916 917 log("Setting up camera " + mCameraId); 918 logIndent(1); 919 920 if (mState < CAMERA_OPEN) { 921 log("Opening camera " + mCameraId); 922 923 try { 924 mCamera = Camera.open(mCameraId); 925 } catch (RuntimeException e) { 926 logE("Exception opening camera: " + e.getMessage()); 927 resetCamera(); 928 mCameraSpinner.setSelection(0); 929 logIndent(-1); 930 return; 931 } 932 mState = CAMERA_OPEN; 933 } 934 935 mCamera.setErrorCallback(this); 936 937 setCameraDisplayOrientation(); 938 mParams = mCamera.getParameters(); 939 940 // Set up preview size selection 941 942 log("Configuring camera"); 943 logIndent(1); 944 945 updatePreviewSizes(mParams); 946 updatePreviewFrameRate(mCameraId); 947 updatePreviewFormats(mParams); 948 updateAfModes(mParams); 949 updateFlashModes(mParams); 950 updateSnapshotSizes(mParams); 951 updateCamcorderProfile(mCameraId); 952 updateVideoRecordSize(mCameraId); 953 updateVideoFrameRate(mCameraId); 954 updateColorEffects(mParams); 955 956 // Trigger updating video record size to match camcorder profile 957 mCamcorderProfileSpinner.setSelection(mCamcorderProfile); 958 959 if (mParams.isVideoStabilizationSupported()) { 960 log("Video stabilization is supported"); 961 mRecordStabilizationToggle.setEnabled(true); 962 } else { 963 log("Video stabilization not supported"); 964 mRecordStabilizationToggle.setEnabled(false); 965 } 966 967 if (mParams.isAutoExposureLockSupported()) { 968 log("Auto-Exposure locking is supported"); 969 mExposureLockToggle.setEnabled(true); 970 } else { 971 log("Auto-Exposure locking is not supported"); 972 mExposureLockToggle.setEnabled(false); 973 } 974 975 if (mParams.isZoomSupported()) { 976 int maxZoom = mParams.getMaxZoom(); 977 mZoomSeekBar.setMax(maxZoom); 978 log("Zoom is supported, set max to " + maxZoom); 979 mZoomSeekBar.setEnabled(true); 980 } else { 981 log("Zoom is not supported"); 982 mZoomSeekBar.setEnabled(false); 983 } 984 985 // Update parameters based on above updates 986 mCamera.setParameters(mParams); 987 988 if (mPreviewHolder != null) { 989 log("Setting preview display"); 990 try { 991 mCamera.setPreviewDisplay(mPreviewHolder); 992 } catch(IOException e) { 993 Log.e(TAG, "Unable to set up preview!"); 994 } 995 } 996 997 logIndent(-1); 998 999 enableOpenOnlyControls(true); 1000 1001 resizePreview(); 1002 if (mPreviewToggle.isChecked()) { 1003 log("Starting preview" ); 1004 mCamera.startPreview(); 1005 mState = CAMERA_PREVIEW; 1006 } else { 1007 mState = CAMERA_OPEN; 1008 enablePreviewOnlyControls(false); 1009 } 1010 logIndent(-1); 1011 } 1012 resetCamera()1013 private void resetCamera() { 1014 if (mState >= CAMERA_OPEN) { 1015 log("Closing old camera"); 1016 mCamera.release(); 1017 } 1018 mCamera = null; 1019 mCameraId = NO_CAMERA_ID; 1020 mState = CAMERA_UNINITIALIZED; 1021 1022 enableOpenOnlyControls(false); 1023 } 1024 updateAfModes(Parameters params)1025 private void updateAfModes(Parameters params) { 1026 mAfModes = params.getSupportedFocusModes(); 1027 1028 mAutofocusModeSpinner.setAdapter( 1029 new ArrayAdapter<String>(this, R.layout.spinner_item, 1030 mAfModes.toArray(new String[0]))); 1031 1032 mAfMode = 0; 1033 1034 params.setFocusMode(mAfModes.get(mAfMode)); 1035 1036 log("Setting AF mode to " + mAfModes.get(mAfMode)); 1037 } 1038 updateFlashModes(Parameters params)1039 private void updateFlashModes(Parameters params) { 1040 mFlashModes = params.getSupportedFlashModes(); 1041 1042 if (mFlashModes != null) { 1043 mFlashModeSpinnerLabel.setVisibility(View.VISIBLE); 1044 mFlashModeSpinner.setVisibility(View.VISIBLE); 1045 mFlashModeSpinner.setAdapter( 1046 new ArrayAdapter<String>(this, R.layout.spinner_item, 1047 mFlashModes.toArray(new String[0]))); 1048 1049 mFlashMode = 0; 1050 1051 params.setFlashMode(mFlashModes.get(mFlashMode)); 1052 1053 log("Setting Flash mode to " + mFlashModes.get(mFlashMode)); 1054 } else { 1055 // this camera has no flash 1056 mFlashModeSpinnerLabel.setVisibility(View.GONE); 1057 mFlashModeSpinner.setVisibility(View.GONE); 1058 } 1059 } 1060 1061 private View.OnClickListener mExposureLockToggleListener = 1062 new View.OnClickListener() { 1063 public void onClick(View v) { 1064 boolean on = ((ToggleButton) v).isChecked(); 1065 log("Auto-Exposure was " + mParams.getAutoExposureLock()); 1066 mParams.setAutoExposureLock(on); 1067 log("Auto-Exposure is now " + mParams.getAutoExposureLock()); 1068 } 1069 }; 1070 1071 private final SeekBar.OnSeekBarChangeListener mZoomSeekBarListener = 1072 new SeekBar.OnSeekBarChangeListener() { 1073 @Override 1074 public void onProgressChanged(SeekBar seekBar, int progress, 1075 boolean fromUser) { 1076 mZoom = progress; 1077 mParams.setZoom(mZoom); 1078 mCamera.setParameters(mParams); 1079 } 1080 @Override 1081 public void onStartTrackingTouch(SeekBar seekBar) { } 1082 @Override 1083 public void onStopTrackingTouch(SeekBar seekBar) { 1084 log("Zoom set to " + mZoom + " / " + mParams.getMaxZoom() + " (" + 1085 ((float)(mParams.getZoomRatios().get(mZoom))/100) + "x)"); 1086 } 1087 }; 1088 updatePreviewSizes(Camera.Parameters params)1089 private void updatePreviewSizes(Camera.Parameters params) { 1090 mPreviewSizes = params.getSupportedPreviewSizes(); 1091 1092 String[] availableSizeNames = new String[mPreviewSizes.size()]; 1093 int i = 0; 1094 for (Camera.Size previewSize: mPreviewSizes) { 1095 availableSizeNames[i++] = 1096 Integer.toString(previewSize.width) + " x " + 1097 Integer.toString(previewSize.height); 1098 } 1099 mPreviewSizeSpinner.setAdapter( 1100 new ArrayAdapter<String>( 1101 this, R.layout.spinner_item, availableSizeNames)); 1102 1103 mPreviewSize = 0; 1104 1105 int width = mPreviewSizes.get(mPreviewSize).width; 1106 int height = mPreviewSizes.get(mPreviewSize).height; 1107 params.setPreviewSize(width, height); 1108 log("Setting preview size to " + width + " x " + height); 1109 } 1110 updatePreviewFrameRate(int cameraId)1111 private void updatePreviewFrameRate(int cameraId) { 1112 List<Integer> frameRates = mParams.getSupportedPreviewFrameRates(); 1113 int defaultPreviewFrameRate = mParams.getPreviewFrameRate(); 1114 1115 List<String> frameRateStrings = new ArrayList<String>(); 1116 mPreviewFrameRates = new ArrayList<Integer>(); 1117 1118 int currentIndex = 0; 1119 for (Integer frameRate : frameRates) { 1120 mPreviewFrameRates.add(frameRate); 1121 if(frameRate == defaultPreviewFrameRate) { 1122 frameRateStrings.add(frameRate.toString() + " (Default)"); 1123 mPreviewFrameRate = currentIndex; 1124 } else { 1125 frameRateStrings.add(frameRate.toString()); 1126 } 1127 currentIndex++; 1128 } 1129 1130 String[] nameArray = (String[])frameRateStrings.toArray(new String[0]); 1131 mPreviewFrameRateSpinner.setAdapter( 1132 new ArrayAdapter<String>( 1133 this, R.layout.spinner_item, nameArray)); 1134 1135 mPreviewFrameRateSpinner.setSelection(mPreviewFrameRate); 1136 log("Setting preview frame rate to " + nameArray[mPreviewFrameRate]); 1137 } 1138 updatePreviewFormats(Camera.Parameters params)1139 private void updatePreviewFormats(Camera.Parameters params) { 1140 mPreviewFormats = params.getSupportedPreviewFormats(); 1141 1142 String[] availableFormatNames = new String[mPreviewFormats.size()]; 1143 int i = 0; 1144 for (Integer previewFormat: mPreviewFormats) { 1145 availableFormatNames[i++] = mFormatNames.get(previewFormat); 1146 } 1147 mCallbackFormatSpinner.setAdapter( 1148 new ArrayAdapter<String>( 1149 this, R.layout.spinner_item, availableFormatNames)); 1150 1151 mPreviewFormat = 0; 1152 mCallbacksEnabled = false; 1153 mCallbackToggle.setChecked(false); 1154 mCallbackView.setVisibility(View.GONE); 1155 1156 params.setPreviewFormat(mPreviewFormats.get(mPreviewFormat)); 1157 log("Setting preview format to " + 1158 mFormatNames.get(mPreviewFormats.get(mPreviewFormat))); 1159 } 1160 updateSnapshotSizes(Camera.Parameters params)1161 private void updateSnapshotSizes(Camera.Parameters params) { 1162 String[] availableSizeNames; 1163 mSnapshotSizes = params.getSupportedPictureSizes(); 1164 1165 availableSizeNames = new String[mSnapshotSizes.size()]; 1166 int i = 0; 1167 for (Camera.Size snapshotSize : mSnapshotSizes) { 1168 availableSizeNames[i++] = 1169 Integer.toString(snapshotSize.width) + " x " + 1170 Integer.toString(snapshotSize.height); 1171 } 1172 mSnapshotSizeSpinner.setAdapter( 1173 new ArrayAdapter<String>( 1174 this, R.layout.spinner_item, availableSizeNames)); 1175 1176 mSnapshotSize = 0; 1177 1178 int snapshotWidth = mSnapshotSizes.get(mSnapshotSize).width; 1179 int snapshotHeight = mSnapshotSizes.get(mSnapshotSize).height; 1180 params.setPictureSize(snapshotWidth, snapshotHeight); 1181 log("Setting snapshot size to " + snapshotWidth + " x " + snapshotHeight); 1182 } 1183 updateCamcorderProfile(int cameraId)1184 private void updateCamcorderProfile(int cameraId) { 1185 // Have to query all of these individually, 1186 final int PROFILES[] = new int[] { 1187 CamcorderProfile.QUALITY_2160P, 1188 CamcorderProfile.QUALITY_1080P, 1189 CamcorderProfile.QUALITY_480P, 1190 CamcorderProfile.QUALITY_720P, 1191 CamcorderProfile.QUALITY_CIF, 1192 CamcorderProfile.QUALITY_HIGH, 1193 CamcorderProfile.QUALITY_LOW, 1194 CamcorderProfile.QUALITY_QCIF, 1195 CamcorderProfile.QUALITY_QVGA, 1196 CamcorderProfile.QUALITY_TIME_LAPSE_2160P, 1197 CamcorderProfile.QUALITY_TIME_LAPSE_1080P, 1198 CamcorderProfile.QUALITY_TIME_LAPSE_480P, 1199 CamcorderProfile.QUALITY_TIME_LAPSE_720P, 1200 CamcorderProfile.QUALITY_TIME_LAPSE_CIF, 1201 CamcorderProfile.QUALITY_TIME_LAPSE_HIGH, 1202 CamcorderProfile.QUALITY_TIME_LAPSE_LOW, 1203 CamcorderProfile.QUALITY_TIME_LAPSE_QCIF, 1204 CamcorderProfile.QUALITY_TIME_LAPSE_QVGA 1205 }; 1206 1207 final String PROFILE_NAMES[] = new String[] { 1208 "2160P", 1209 "1080P", 1210 "480P", 1211 "720P", 1212 "CIF", 1213 "HIGH", 1214 "LOW", 1215 "QCIF", 1216 "QVGA", 1217 "TIME_LAPSE_2160P", 1218 "TIME_LAPSE_1080P", 1219 "TIME_LAPSE_480P", 1220 "TIME_LAPSE_720P", 1221 "TIME_LAPSE_CIF", 1222 "TIME_LAPSE_HIGH", 1223 "TIME_LAPSE_LOW", 1224 "TIME_LAPSE_QCIF", 1225 "TIME_LAPSE_QVGA" 1226 }; 1227 1228 List<String> availableCamcorderProfileNames = new ArrayList<String>(); 1229 mCamcorderProfiles = new ArrayList<CamcorderProfile>(); 1230 1231 for (int i = 0; i < PROFILES.length; i++) { 1232 if (CamcorderProfile.hasProfile(cameraId, PROFILES[i])) { 1233 availableCamcorderProfileNames.add(PROFILE_NAMES[i]); 1234 mCamcorderProfiles.add(CamcorderProfile.get(cameraId, PROFILES[i])); 1235 } 1236 } 1237 String[] nameArray = (String[])availableCamcorderProfileNames.toArray(new String[0]); 1238 mCamcorderProfileSpinner.setAdapter( 1239 new ArrayAdapter<String>( 1240 this, R.layout.spinner_item, nameArray)); 1241 1242 mCamcorderProfile = 0; 1243 log("Setting camcorder profile to " + nameArray[mCamcorderProfile]); 1244 1245 } 1246 updateVideoRecordSize(int cameraId)1247 private void updateVideoRecordSize(int cameraId) { 1248 List<Camera.Size> videoSizes = mParams.getSupportedVideoSizes(); 1249 if (videoSizes == null) { // TODO: surface this to the user 1250 log("Failed to get video size list, using preview sizes instead"); 1251 videoSizes = mParams.getSupportedPreviewSizes(); 1252 } 1253 1254 List<String> availableVideoRecordSizes = new ArrayList<String>(); 1255 mVideoRecordSizes = new ArrayList<Camera.Size>(); 1256 1257 availableVideoRecordSizes.add("Default"); 1258 mVideoRecordSizes.add(mCamera.new Size(0,0)); 1259 1260 for (Camera.Size s : videoSizes) { 1261 availableVideoRecordSizes.add(s.width + "x" + s.height); 1262 mVideoRecordSizes.add(s); 1263 } 1264 String[] nameArray = (String[])availableVideoRecordSizes.toArray(new String[0]); 1265 mVideoRecordSizeSpinner.setAdapter( 1266 new ArrayAdapter<String>( 1267 this, R.layout.spinner_item, nameArray)); 1268 1269 mVideoRecordSize = 0; 1270 log("Setting video record profile to " + nameArray[mVideoRecordSize]); 1271 } 1272 updateVideoFrameRate(int cameraId)1273 private void updateVideoFrameRate(int cameraId) { 1274 // Use preview framerates as video framerates 1275 List<Integer> frameRates = mParams.getSupportedPreviewFrameRates(); 1276 1277 List<String> frameRateStrings = new ArrayList<String>(); 1278 mVideoFrameRates = new ArrayList<Integer>(); 1279 1280 frameRateStrings.add("Default"); 1281 mVideoFrameRates.add(0); 1282 1283 for (Integer frameRate : frameRates) { 1284 frameRateStrings.add(frameRate.toString()); 1285 mVideoFrameRates.add(frameRate); 1286 } 1287 String[] nameArray = (String[])frameRateStrings.toArray(new String[0]); 1288 mVideoFrameRateSpinner.setAdapter( 1289 new ArrayAdapter<String>( 1290 this, R.layout.spinner_item, nameArray)); 1291 1292 mVideoFrameRate = 0; 1293 log("Setting recording frame rate to " + nameArray[mVideoFrameRate]); 1294 } 1295 resizePreview()1296 void resizePreview() { 1297 // Reset preview layout parameters, to trigger layout pass 1298 // This will eventually call layoutPreview below 1299 Resources res = getResources(); 1300 mPreviewView.setLayoutParams( 1301 new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0, 1302 mCallbacksEnabled ? 1303 res.getInteger(R.integer.preview_with_callback_weight): 1304 res.getInteger(R.integer.preview_only_weight) )); 1305 } 1306 layoutPreview()1307 void layoutPreview() { 1308 int width = mPreviewSizes.get(mPreviewSize).width; 1309 int height = mPreviewSizes.get(mPreviewSize).height; 1310 float previewAspect = ((float) width) / height; 1311 1312 int viewHeight = mPreviewView.getHeight(); 1313 int viewWidth = mPreviewView.getWidth(); 1314 float viewAspect = ((float) viewWidth) / viewHeight; 1315 if ( previewAspect > viewAspect) { 1316 viewHeight = (int) (viewWidth / previewAspect); 1317 } else { 1318 viewWidth = (int) (viewHeight * previewAspect); 1319 } 1320 mPreviewView.setLayoutParams( 1321 new LayoutParams(viewWidth, viewHeight)); 1322 1323 if (mCallbacksEnabled) { 1324 int callbackHeight = mCallbackView.getHeight(); 1325 int callbackWidth = mCallbackView.getWidth(); 1326 float callbackAspect = ((float) callbackWidth) / callbackHeight; 1327 if ( previewAspect > callbackAspect) { 1328 callbackHeight = (int) (callbackWidth / previewAspect); 1329 } else { 1330 callbackWidth = (int) (callbackHeight * previewAspect); 1331 } 1332 mCallbackView.setLayoutParams( 1333 new LayoutParams(callbackWidth, callbackHeight)); 1334 configureCallbacks(callbackWidth, callbackHeight); 1335 } 1336 } 1337 1338 configureCallbacks(int callbackWidth, int callbackHeight)1339 private void configureCallbacks(int callbackWidth, int callbackHeight) { 1340 if (mState >= CAMERA_OPEN && mCallbacksEnabled) { 1341 mCamera.setPreviewCallbackWithBuffer(null); 1342 int width = mPreviewSizes.get(mPreviewSize).width; 1343 int height = mPreviewSizes.get(mPreviewSize).height; 1344 int format = mPreviewFormats.get(mPreviewFormat); 1345 1346 mCallbackProcessor = new CallbackProcessor(width, height, format, 1347 getResources(), mCallbackView, 1348 callbackWidth, callbackHeight, mRS); 1349 1350 int size = getCallbackBufferSize(width, height, format); 1351 log("Configuring callbacks:" + width + " x " + height + 1352 " , format " + format); 1353 for (int i = 0; i < CALLBACK_BUFFER_COUNT; i++) { 1354 mCamera.addCallbackBuffer(new byte[size]); 1355 } 1356 mCamera.setPreviewCallbackWithBuffer(this); 1357 } 1358 mLastCallbackTimestamp = -1; 1359 mCallbackFrameCount = 0; 1360 mCallbackAvgFrameDuration = 30; 1361 } 1362 stopCallbacks()1363 private void stopCallbacks() { 1364 if (mState >= CAMERA_OPEN) { 1365 mCamera.setPreviewCallbackWithBuffer(null); 1366 if (mCallbackProcessor != null) { 1367 if (!mCallbackProcessor.stop()) { 1368 logE("Can't stop preview callback processing!"); 1369 } 1370 } 1371 } 1372 } 1373 1374 @Override onPreviewFrame(byte[] data, Camera camera)1375 public void onPreviewFrame(byte[] data, Camera camera) { 1376 long timestamp = SystemClock.elapsedRealtime(); 1377 if (mLastCallbackTimestamp != -1) { 1378 long frameDuration = timestamp - mLastCallbackTimestamp; 1379 mCallbackAvgFrameDuration = 1380 mCallbackAvgFrameDuration * MEAN_FPS_HISTORY_COEFF + 1381 frameDuration * MEAN_FPS_MEASUREMENT_COEFF; 1382 } 1383 mLastCallbackTimestamp = timestamp; 1384 if (mState < CAMERA_PREVIEW || !mCallbacksEnabled) { 1385 mCamera.addCallbackBuffer(data); 1386 return; 1387 } 1388 mCallbackFrameCount++; 1389 if (mCallbackFrameCount % FPS_REPORTING_PERIOD == 0) { 1390 log("Got " + FPS_REPORTING_PERIOD + " callback frames, fps " 1391 + 1e3/mCallbackAvgFrameDuration); 1392 } 1393 mCallbackProcessor.displayCallback(data); 1394 1395 mCamera.addCallbackBuffer(data); 1396 } 1397 1398 @Override onError(int error, Camera camera)1399 public void onError(int error, Camera camera) { 1400 String errorName; 1401 switch (error) { 1402 case Camera.CAMERA_ERROR_SERVER_DIED: 1403 errorName = "SERVER_DIED"; 1404 break; 1405 case Camera.CAMERA_ERROR_UNKNOWN: 1406 errorName = "UNKNOWN"; 1407 break; 1408 default: 1409 errorName = "?"; 1410 break; 1411 } 1412 logE("Camera error received: " + errorName + " (" + error + ")" ); 1413 logE("Shutting down camera"); 1414 resetCamera(); 1415 mCameraSpinner.setSelection(0); 1416 } 1417 1418 static final int MEDIA_TYPE_IMAGE = 0; 1419 static final int MEDIA_TYPE_VIDEO = 1; 1420 @SuppressLint("SimpleDateFormat") getOutputMediaFile(int type)1421 File getOutputMediaFile(int type){ 1422 // To be safe, you should check that the SDCard is mounted 1423 // using Environment.getExternalStorageState() before doing this. 1424 1425 String state = Environment.getExternalStorageState(); 1426 if (!Environment.MEDIA_MOUNTED.equals(state)) { 1427 return null; 1428 } 1429 1430 File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory( 1431 Environment.DIRECTORY_DCIM), "TestingCamera"); 1432 // This location works best if you want the created images to be shared 1433 // between applications and persist after your app has been uninstalled. 1434 1435 // Create the storage directory if it does not exist 1436 if (! mediaStorageDir.exists()){ 1437 if (! mediaStorageDir.mkdirs()){ 1438 logE("Failed to create directory for pictures/video"); 1439 return null; 1440 } 1441 } 1442 1443 // Create a media file name 1444 String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); 1445 File mediaFile; 1446 if (type == MEDIA_TYPE_IMAGE){ 1447 mediaFile = new File(mediaStorageDir.getPath() + File.separator + 1448 "IMG_"+ timeStamp + ".jpg"); 1449 } else if(type == MEDIA_TYPE_VIDEO) { 1450 mediaFile = new File(mediaStorageDir.getPath() + File.separator + 1451 "VID_"+ timeStamp + ".mp4"); 1452 } else { 1453 return null; 1454 } 1455 1456 return mediaFile; 1457 } 1458 notifyMediaScannerOfFile(File newFile, final MediaScannerConnection.OnScanCompletedListener listener)1459 void notifyMediaScannerOfFile(File newFile, 1460 final MediaScannerConnection.OnScanCompletedListener listener) { 1461 final Handler h = new Handler(); 1462 MediaScannerConnection.scanFile(this, 1463 new String[] { newFile.toString() }, 1464 null, 1465 new MediaScannerConnection.OnScanCompletedListener() { 1466 @Override 1467 public void onScanCompleted(final String path, final Uri uri) { 1468 h.post(new Runnable() { 1469 @Override 1470 public void run() { 1471 log("MediaScanner notified: " + 1472 path + " -> " + uri); 1473 if (listener != null) 1474 listener.onScanCompleted(path, uri); 1475 } 1476 }); 1477 } 1478 }); 1479 } 1480 deleteFile(File badFile)1481 private void deleteFile(File badFile) { 1482 if (badFile.exists()) { 1483 boolean success = badFile.delete(); 1484 if (success) log("Deleted file " + badFile.toString()); 1485 else log("Unable to delete file " + badFile.toString()); 1486 } 1487 } 1488 startRecording()1489 private void startRecording() { 1490 log("Starting recording"); 1491 logIndent(1); 1492 log("Configuring MediaRecoder"); 1493 1494 mRecordHandoffCheckBox.setEnabled(false); 1495 if (mRecordHandoffCheckBox.isChecked()) { 1496 mCamera.release(); 1497 } else { 1498 mCamera.unlock(); 1499 } 1500 1501 if (mRecorder != null) { 1502 mRecorder.release(); 1503 } 1504 1505 mRecorder = new MediaRecorder(); 1506 mRecorder.setOnErrorListener(mRecordingErrorListener); 1507 mRecorder.setOnInfoListener(mRecordingInfoListener); 1508 if (!mRecordHandoffCheckBox.isChecked()) { 1509 mRecorder.setCamera(mCamera); 1510 } 1511 mRecorder.setPreviewDisplay(mPreviewHolder.getSurface()); 1512 1513 mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); 1514 mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 1515 mRecorder.setProfile(mCamcorderProfiles.get(mCamcorderProfile)); 1516 Camera.Size videoRecordSize = mVideoRecordSizes.get(mVideoRecordSize); 1517 if (videoRecordSize.width > 0 && videoRecordSize.height > 0) { 1518 mRecorder.setVideoSize(videoRecordSize.width, videoRecordSize.height); 1519 } 1520 if (mVideoFrameRates.get(mVideoFrameRate) > 0) { 1521 mRecorder.setVideoFrameRate(mVideoFrameRates.get(mVideoFrameRate)); 1522 } 1523 File outputFile = getOutputMediaFile(MEDIA_TYPE_VIDEO); 1524 log("File name:" + outputFile.toString()); 1525 mRecorder.setOutputFile(outputFile.toString()); 1526 1527 boolean ready = false; 1528 log("Preparing MediaRecorder"); 1529 try { 1530 mRecorder.prepare(); 1531 ready = true; 1532 } catch (Exception e) { 1533 StringWriter writer = new StringWriter(); 1534 e.printStackTrace(new PrintWriter(writer)); 1535 logE("Exception preparing MediaRecorder:\n" + writer.toString()); 1536 } 1537 1538 if (ready) { 1539 try { 1540 log("Starting MediaRecorder"); 1541 mRecorder.start(); 1542 mState = CAMERA_RECORD; 1543 log("Recording active"); 1544 mRecordingFile = outputFile; 1545 } catch (Exception e) { 1546 StringWriter writer = new StringWriter(); 1547 e.printStackTrace(new PrintWriter(writer)); 1548 logE("Exception starting MediaRecorder:\n" + writer.toString()); 1549 ready = false; 1550 } 1551 } 1552 1553 if (!ready) { 1554 mRecordToggle.setChecked(false); 1555 mRecordHandoffCheckBox.setEnabled(true); 1556 1557 if (mRecordHandoffCheckBox.isChecked()) { 1558 mState = CAMERA_UNINITIALIZED; 1559 setUpCamera(); 1560 } 1561 } 1562 logIndent(-1); 1563 } 1564 1565 private MediaRecorder.OnErrorListener mRecordingErrorListener = 1566 new MediaRecorder.OnErrorListener() { 1567 @Override 1568 public void onError(MediaRecorder mr, int what, int extra) { 1569 logE("MediaRecorder reports error: " + what + ", extra " 1570 + extra); 1571 if (mState == CAMERA_RECORD) { 1572 stopRecording(true); 1573 } 1574 } 1575 }; 1576 1577 private MediaRecorder.OnInfoListener mRecordingInfoListener = 1578 new MediaRecorder.OnInfoListener() { 1579 @Override 1580 public void onInfo(MediaRecorder mr, int what, int extra) { 1581 log("MediaRecorder reports info: " + what + ", extra " 1582 + extra); 1583 } 1584 }; 1585 stopRecording(boolean error)1586 private void stopRecording(boolean error) { 1587 log("Stopping recording"); 1588 mRecordHandoffCheckBox.setEnabled(true); 1589 mRecordToggle.setChecked(false); 1590 if (mRecorder != null) { 1591 try { 1592 mRecorder.stop(); 1593 } catch (RuntimeException e) { 1594 // this can happen if there were no frames received by recorder 1595 logE("Could not create output file"); 1596 error = true; 1597 } 1598 1599 if (mRecordHandoffCheckBox.isChecked()) { 1600 mState = CAMERA_UNINITIALIZED; 1601 setUpCamera(); 1602 } else { 1603 mCamera.lock(); 1604 mState = CAMERA_PREVIEW; 1605 } 1606 1607 if (!error) { 1608 notifyMediaScannerOfFile(mRecordingFile, null); 1609 } else { 1610 deleteFile(mRecordingFile); 1611 } 1612 mRecordingFile = null; 1613 } else { 1614 logE("Recorder is unexpectedly null!"); 1615 } 1616 } 1617 getCallbackBufferSize(int width, int height, int format)1618 static int getCallbackBufferSize(int width, int height, int format) { 1619 int size = -1; 1620 switch (format) { 1621 case ImageFormat.NV21: 1622 size = width * height * 3 / 2; 1623 break; 1624 case ImageFormat.YV12: 1625 int y_stride = (int) (Math.ceil( width / 16.) * 16); 1626 int y_size = y_stride * height; 1627 int c_stride = (int) (Math.ceil(y_stride / 32.) * 16); 1628 int c_size = c_stride * height/2; 1629 size = y_size + c_size * 2; 1630 break; 1631 case ImageFormat.NV16: 1632 case ImageFormat.RGB_565: 1633 case ImageFormat.YUY2: 1634 size = 2 * width * height; 1635 break; 1636 case ImageFormat.JPEG: 1637 Log.e(TAG, "JPEG callback buffers not supported!"); 1638 size = 0; 1639 break; 1640 case ImageFormat.UNKNOWN: 1641 Log.e(TAG, "Unknown-format callback buffers not supported!"); 1642 size = 0; 1643 break; 1644 } 1645 return size; 1646 } 1647 1648 private OnItemSelectedListener mColorEffectListener = 1649 new OnItemSelectedListener() { 1650 @Override 1651 public void onItemSelected(AdapterView<?> parent, 1652 View view, int pos, long id) { 1653 if (pos == mColorEffect) return; 1654 1655 mColorEffect = pos; 1656 String colorEffect = mColorEffects.get(mColorEffect); 1657 log("Setting color effect to " + colorEffect); 1658 mParams.setColorEffect(colorEffect); 1659 mCamera.setParameters(mParams); 1660 } 1661 1662 @Override 1663 public void onNothingSelected(AdapterView<?> arg0) { 1664 } 1665 }; 1666 updateColorEffects(Parameters params)1667 private void updateColorEffects(Parameters params) { 1668 mColorEffects = params.getSupportedColorEffects(); 1669 if (mColorEffects != null) { 1670 mColorEffectSpinnerLabel.setVisibility(View.VISIBLE); 1671 mColorEffectSpinner.setVisibility(View.VISIBLE); 1672 mColorEffectSpinner.setAdapter( 1673 new ArrayAdapter<String>(this, R.layout.spinner_item, 1674 mColorEffects.toArray(new String[0]))); 1675 mColorEffect = 0; 1676 params.setColorEffect(mColorEffects.get(mColorEffect)); 1677 log("Setting Color Effect to " + mColorEffects.get(mColorEffect)); 1678 } else { 1679 mColorEffectSpinnerLabel.setVisibility(View.GONE); 1680 mColorEffectSpinner.setVisibility(View.GONE); 1681 } 1682 } 1683 1684 private int mLogIndentLevel = 0; 1685 private String mLogIndent = "\t"; 1686 /** Increment or decrement log indentation level */ logIndent(int delta)1687 synchronized void logIndent(int delta) { 1688 mLogIndentLevel += delta; 1689 if (mLogIndentLevel < 0) mLogIndentLevel = 0; 1690 char[] mLogIndentArray = new char[mLogIndentLevel + 1]; 1691 for (int i = -1; i < mLogIndentLevel; i++) { 1692 mLogIndentArray[i + 1] = '\t'; 1693 } 1694 mLogIndent = new String(mLogIndentArray); 1695 } 1696 1697 @SuppressLint("SimpleDateFormat") 1698 SimpleDateFormat mDateFormatter = new SimpleDateFormat("HH:mm:ss.SSS"); 1699 /** Log both to log text view and to device logcat */ log(String logLine)1700 void log(String logLine) { 1701 Log.d(TAG, logLine); 1702 logAndScrollToBottom(logLine, mLogIndent); 1703 } 1704 logE(String logLine)1705 void logE(String logLine) { 1706 Log.e(TAG, logLine); 1707 logAndScrollToBottom(logLine, mLogIndent + "!!! "); 1708 } 1709 logAndScrollToBottom(String logLine, String logIndent)1710 synchronized private void logAndScrollToBottom(String logLine, String logIndent) { 1711 StringBuffer logEntry = new StringBuffer(32); 1712 logEntry.append("\n").append(mDateFormatter.format(new Date())).append(logIndent); 1713 logEntry.append(logLine); 1714 mLogView.append(logEntry); 1715 final Layout layout = mLogView.getLayout(); 1716 if (layout != null){ 1717 int scrollDelta = layout.getLineBottom(mLogView.getLineCount() - 1) 1718 - mLogView.getScrollY() - mLogView.getHeight(); 1719 if(scrollDelta > 0) { 1720 mLogView.scrollBy(0, scrollDelta); 1721 } 1722 } 1723 } 1724 } 1725