TestingCamera.java revision b95f05fb03d0e9192926f8d46c02602a8035b721
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 17package com.android.testingcamera; 18 19import android.app.Activity; 20import android.app.FragmentManager; 21import android.hardware.Camera; 22import android.hardware.Camera.Parameters; 23import android.media.CamcorderProfile; 24import android.media.MediaRecorder; 25import android.media.MediaScannerConnection; 26import android.net.Uri; 27import android.os.Bundle; 28import android.os.Environment; 29import android.os.Handler; 30import android.view.View; 31import android.view.SurfaceHolder; 32import android.view.SurfaceView; 33import android.view.View.OnClickListener; 34import android.widget.AdapterView; 35import android.widget.AdapterView.OnItemSelectedListener; 36import android.widget.ArrayAdapter; 37import android.widget.Button; 38import android.widget.LinearLayout.LayoutParams; 39import android.widget.Spinner; 40import android.widget.TextView; 41import android.widget.ToggleButton; 42import android.text.Layout; 43import android.text.method.ScrollingMovementMethod; 44import android.util.Log; 45 46import java.io.File; 47import java.io.IOException; 48import java.io.PrintWriter; 49import java.io.StringWriter; 50import java.text.SimpleDateFormat; 51import java.util.ArrayList; 52import java.util.Date; 53import java.util.HashSet; 54import java.util.List; 55import java.util.Set; 56 57/** 58 * A simple test application for the camera API. 59 * 60 * The goal of this application is to allow all camera API features to be 61 * exercised, and all information provided by the API to be shown. 62 */ 63public class TestingCamera extends Activity implements SurfaceHolder.Callback { 64 65 /** UI elements */ 66 private SurfaceView mPreviewView; 67 private SurfaceHolder mPreviewHolder; 68 69 private Spinner mCameraSpinner; 70 private Button mInfoButton; 71 private Spinner mPreviewSizeSpinner; 72 private ToggleButton mPreviewToggle; 73 private Spinner mAutofocusModeSpinner; 74 private Button mAutofocusButton; 75 private Button mCancelAutofocusButton; 76 private Spinner mFlashModeSpinner; 77 private ToggleButton mExposureLockToggle; 78 private Spinner mSnapshotSizeSpinner; 79 private Button mTakePictureButton; 80 private Spinner mCamcorderProfileSpinner; 81 private Spinner mVideoRecordSizeSpinner; 82 private Spinner mVideoFrameRateSpinner; 83 private ToggleButton mRecordToggle; 84 private ToggleButton mRecordStabilizationToggle; 85 86 private TextView mLogView; 87 88 private Set<View> mPreviewOnlyControls = new HashSet<View>(); 89 90 /** Camera state */ 91 private int mCameraId = 0; 92 private Camera mCamera; 93 private Camera.Parameters mParams; 94 private List<Camera.Size> mPreviewSizes; 95 private int mPreviewSize = 0; 96 private List<String> mAfModes; 97 private int mAfMode = 0; 98 private List<String> mFlashModes; 99 private int mFlashMode = 0; 100 private List<Camera.Size> mSnapshotSizes; 101 private int mSnapshotSize = 0; 102 private List<CamcorderProfile> mCamcorderProfiles; 103 private int mCamcorderProfile = 0; 104 private List<Camera.Size> mVideoRecordSizes; 105 private int mVideoRecordSize = 0; 106 private List<Integer> mVideoFrameRates; 107 private int mVideoFrameRate = 0; 108 109 private MediaRecorder mRecorder; 110 private File mRecordingFile; 111 112 private static final int CAMERA_UNINITIALIZED = 0; 113 private static final int CAMERA_OPEN = 1; 114 private static final int CAMERA_PREVIEW = 2; 115 private static final int CAMERA_TAKE_PICTURE = 3; 116 private static final int CAMERA_RECORD = 4; 117 private int mState = CAMERA_UNINITIALIZED; 118 119 /** Misc variables */ 120 121 private static final String TAG = "TestingCamera"; 122 123 /** Activity lifecycle */ 124 125 @Override 126 public void onCreate(Bundle savedInstanceState) { 127 super.onCreate(savedInstanceState); 128 129 setContentView(R.layout.main); 130 131 mPreviewView = (SurfaceView)findViewById(R.id.preview); 132 mPreviewView.getHolder().addCallback(this); 133 134 mCameraSpinner = (Spinner) findViewById(R.id.camera_spinner); 135 mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener); 136 137 mInfoButton = (Button) findViewById(R.id.info_button); 138 mInfoButton.setOnClickListener(mInfoButtonListener); 139 140 mPreviewSizeSpinner = (Spinner) findViewById(R.id.preview_size_spinner); 141 mPreviewSizeSpinner.setOnItemSelectedListener(mPreviewSizeListener); 142 143 mPreviewToggle = (ToggleButton) findViewById(R.id.start_preview); 144 mPreviewToggle.setOnClickListener(mPreviewToggleListener); 145 146 mAutofocusModeSpinner = (Spinner) findViewById(R.id.af_mode_spinner); 147 mAutofocusModeSpinner.setOnItemSelectedListener(mAutofocusModeListener); 148 149 mAutofocusButton = (Button) findViewById(R.id.af_button); 150 mAutofocusButton.setOnClickListener(mAutofocusButtonListener); 151 mPreviewOnlyControls.add(mAutofocusButton); 152 153 mCancelAutofocusButton = (Button) findViewById(R.id.af_cancel_button); 154 mCancelAutofocusButton.setOnClickListener(mCancelAutofocusButtonListener); 155 mPreviewOnlyControls.add(mCancelAutofocusButton); 156 157 mFlashModeSpinner = (Spinner) findViewById(R.id.flash_mode_spinner); 158 mFlashModeSpinner.setOnItemSelectedListener(mFlashModeListener); 159 160 mExposureLockToggle = (ToggleButton) findViewById(R.id.exposure_lock); 161 mExposureLockToggle.setOnClickListener(mExposureLockToggleListener); 162 163 mSnapshotSizeSpinner = (Spinner) findViewById(R.id.snapshot_size_spinner); 164 mSnapshotSizeSpinner.setOnItemSelectedListener(mSnapshotSizeListener); 165 166 mTakePictureButton = (Button) findViewById(R.id.take_picture); 167 mTakePictureButton.setOnClickListener(mTakePictureListener); 168 mPreviewOnlyControls.add(mTakePictureButton); 169 170 mCamcorderProfileSpinner = (Spinner) findViewById(R.id.camcorder_profile_spinner); 171 mCamcorderProfileSpinner.setOnItemSelectedListener(mCamcorderProfileListener); 172 173 mVideoRecordSizeSpinner = (Spinner) findViewById(R.id.video_record_size_spinner); 174 mVideoRecordSizeSpinner.setOnItemSelectedListener(mVideoRecordSizeListener); 175 176 mVideoFrameRateSpinner = (Spinner) findViewById(R.id.video_frame_rate_spinner); 177 mVideoFrameRateSpinner.setOnItemSelectedListener(mVideoFrameRateListener); 178 179 mRecordToggle = (ToggleButton) findViewById(R.id.start_record); 180 mRecordToggle.setOnClickListener(mRecordToggleListener); 181 mPreviewOnlyControls.add(mRecordToggle); 182 183 mRecordStabilizationToggle = (ToggleButton) findViewById(R.id.record_stabilization); 184 mRecordStabilizationToggle.setOnClickListener(mRecordStabilizationToggleListener); 185 186 mLogView = (TextView) findViewById(R.id.log); 187 mLogView.setMovementMethod(new ScrollingMovementMethod()); 188 189 int numCameras = Camera.getNumberOfCameras(); 190 String[] cameraNames = new String[numCameras]; 191 for (int i = 0; i < numCameras; i++) { 192 cameraNames[i] = "Camera " + i; 193 } 194 195 mCameraSpinner.setAdapter( 196 new ArrayAdapter<String>(this, 197 R.layout.spinner_item, cameraNames)); 198 } 199 200 @Override 201 public void onResume() { 202 super.onResume(); 203 log("onResume: Setting up camera"); 204 mPreviewHolder = null; 205 setUpCamera(); 206 } 207 208 @Override 209 public void onPause() { 210 super.onPause(); 211 log("onPause: Releasing camera"); 212 mCamera.release(); 213 mState = CAMERA_UNINITIALIZED; 214 } 215 216 /** SurfaceHolder.Callback methods */ 217 public void surfaceChanged(SurfaceHolder holder, 218 int format, 219 int width, 220 int height) { 221 if (mPreviewHolder != null) return; 222 223 log("Surface holder available: " + width + " x " + height); 224 mPreviewHolder = holder; 225 try { 226 mCamera.setPreviewDisplay(holder); 227 } catch (IOException e) { 228 logE("Unable to set up preview!"); 229 } 230 } 231 232 public void surfaceCreated(SurfaceHolder holder) { 233 234 } 235 236 public void surfaceDestroyed(SurfaceHolder holder) { 237 mPreviewHolder = null; 238 } 239 240 /** UI controls enable/disable */ 241 private void enablePreviewOnlyControls(boolean enabled) { 242 for (View v : mPreviewOnlyControls) { 243 v.setEnabled(enabled); 244 } 245 } 246 247 /** UI listeners */ 248 249 private AdapterView.OnItemSelectedListener mCameraSpinnerListener = 250 new AdapterView.OnItemSelectedListener() { 251 public void onItemSelected(AdapterView<?> parent, 252 View view, int pos, long id) { 253 if (mCameraId != pos) { 254 mCameraId = pos; 255 setUpCamera(); 256 } 257 } 258 259 public void onNothingSelected(AdapterView<?> parent) { 260 261 } 262 }; 263 264 private OnClickListener mInfoButtonListener = new OnClickListener() { 265 public void onClick(View v) { 266 FragmentManager fm = getFragmentManager(); 267 InfoDialogFragment infoDialog = new InfoDialogFragment(); 268 infoDialog.updateInfo(mCameraId, mCamera); 269 infoDialog.show(fm, "info_dialog_fragment"); 270 } 271 }; 272 273 private AdapterView.OnItemSelectedListener mPreviewSizeListener = 274 new AdapterView.OnItemSelectedListener() { 275 public void onItemSelected(AdapterView<?> parent, 276 View view, int pos, long id) { 277 if (pos == mPreviewSize) return; 278 if (mState == CAMERA_PREVIEW) { 279 log("Stopping preview to switch resolutions"); 280 mCamera.stopPreview(); 281 } 282 283 mPreviewSize = pos; 284 int width = mPreviewSizes.get(mPreviewSize).width; 285 int height = mPreviewSizes.get(mPreviewSize).height; 286 mParams.setPreviewSize(width, height); 287 288 log("Setting preview size to " + width + "x" + height); 289 290 mCamera.setParameters(mParams); 291 292 if (mState == CAMERA_PREVIEW) { 293 log("Restarting preview"); 294 resizePreview(width, height); 295 mCamera.startPreview(); 296 } 297 } 298 299 public void onNothingSelected(AdapterView<?> parent) { 300 301 } 302 }; 303 304 private View.OnClickListener mPreviewToggleListener = 305 new View.OnClickListener() { 306 public void onClick(View v) { 307 if (mState == CAMERA_TAKE_PICTURE) { 308 logE("Can't change preview state while taking picture!"); 309 return; 310 } 311 if (mPreviewToggle.isChecked()) { 312 log("Starting preview"); 313 resizePreview(mPreviewSizes.get(mPreviewSize).width, 314 mPreviewSizes.get(mPreviewSize).height); 315 mCamera.startPreview(); 316 mState = CAMERA_PREVIEW; 317 enablePreviewOnlyControls(true); 318 } else { 319 log("Stopping preview"); 320 mCamera.stopPreview(); 321 mState = CAMERA_OPEN; 322 323 enablePreviewOnlyControls(false); 324 } 325 } 326 }; 327 328 private OnItemSelectedListener mAutofocusModeListener = 329 new OnItemSelectedListener() { 330 public void onItemSelected(AdapterView<?> parent, 331 View view, int pos, long id) { 332 if (pos == mAfMode) return; 333 334 mAfMode = pos; 335 String focusMode = mAfModes.get(mAfMode); 336 log("Setting focus mode to " + focusMode); 337 if (focusMode == Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE || 338 focusMode == Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO) { 339 mCamera.setAutoFocusMoveCallback(mAutofocusMoveCallback); 340 } 341 mParams.setFocusMode(focusMode); 342 343 mCamera.setParameters(mParams); 344 } 345 346 public void onNothingSelected(AdapterView<?> arg0) { 347 348 } 349 }; 350 351 private OnClickListener mAutofocusButtonListener = 352 new View.OnClickListener() { 353 public void onClick(View v) { 354 log("Triggering autofocus"); 355 mCamera.autoFocus(mAutofocusCallback); 356 } 357 }; 358 359 private OnClickListener mCancelAutofocusButtonListener = 360 new View.OnClickListener() { 361 public void onClick(View v) { 362 log("Cancelling autofocus"); 363 mCamera.cancelAutoFocus(); 364 } 365 }; 366 367 private Camera.AutoFocusCallback mAutofocusCallback = 368 new Camera.AutoFocusCallback() { 369 public void onAutoFocus(boolean success, Camera camera) { 370 log("Autofocus completed: " + (success ? "success" : "failure") ); 371 } 372 }; 373 374 private Camera.AutoFocusMoveCallback mAutofocusMoveCallback = 375 new Camera.AutoFocusMoveCallback() { 376 public void onAutoFocusMoving(boolean start, Camera camera) { 377 log("Autofocus movement: " + (start ? "starting" : "stopped") ); 378 } 379 }; 380 381 private OnItemSelectedListener mFlashModeListener = 382 new OnItemSelectedListener() { 383 public void onItemSelected(AdapterView<?> parent, 384 View view, int pos, long id) { 385 if (pos == mFlashMode) return; 386 387 mFlashMode = pos; 388 String flashMode = mFlashModes.get(mFlashMode); 389 log("Setting flash mode to " + flashMode); 390 mParams.setFlashMode(flashMode); 391 mCamera.setParameters(mParams); 392 } 393 394 public void onNothingSelected(AdapterView<?> arg0) { 395 396 } 397 }; 398 399 400 private AdapterView.OnItemSelectedListener mSnapshotSizeListener = 401 new AdapterView.OnItemSelectedListener() { 402 public void onItemSelected(AdapterView<?> parent, 403 View view, int pos, long id) { 404 if (pos == mSnapshotSize) return; 405 406 mSnapshotSize = pos; 407 int width = mSnapshotSizes.get(mSnapshotSize).width; 408 int height = mSnapshotSizes.get(mSnapshotSize).height; 409 log("Setting snapshot size to " + width + " x " + height); 410 411 mParams.setPictureSize(width, height); 412 413 mCamera.setParameters(mParams); 414 } 415 416 public void onNothingSelected(AdapterView<?> parent) { 417 418 } 419 }; 420 421 private View.OnClickListener mTakePictureListener = 422 new View.OnClickListener() { 423 public void onClick(View v) { 424 log("Taking picture"); 425 if (mState == CAMERA_PREVIEW) { 426 mState = CAMERA_TAKE_PICTURE; 427 enablePreviewOnlyControls(false); 428 mPreviewToggle.setChecked(false); 429 430 mCamera.takePicture(mShutterCb, mRawCb, mPostviewCb, mJpegCb); 431 } else { 432 logE("Can't take picture while not running preview!"); 433 } 434 } 435 }; 436 437 private AdapterView.OnItemSelectedListener mCamcorderProfileListener = 438 new AdapterView.OnItemSelectedListener() { 439 public void onItemSelected(AdapterView<?> parent, 440 View view, int pos, long id) { 441 if (pos != mCamcorderProfile) { 442 log("Setting camcorder profile to " + ((TextView)view).getText()); 443 mCamcorderProfile = pos; 444 } 445 446 // Additionally change video recording size to match 447 mVideoRecordSize = 0; // "default", in case it's not found 448 int width = mCamcorderProfiles.get(pos).videoFrameWidth; 449 int height = mCamcorderProfiles.get(pos).videoFrameHeight; 450 for (int i = 0; i < mVideoRecordSizes.size(); i++) { 451 Camera.Size s = mVideoRecordSizes.get(i); 452 if (width == s.width && height == s.height) { 453 mVideoRecordSize = i; 454 break; 455 } 456 } 457 log("Setting video record size to " + mVideoRecordSize); 458 mVideoRecordSizeSpinner.setSelection(mVideoRecordSize); 459 } 460 461 public void onNothingSelected(AdapterView<?> parent) { 462 463 } 464 }; 465 466 private AdapterView.OnItemSelectedListener mVideoRecordSizeListener = 467 new AdapterView.OnItemSelectedListener() { 468 public void onItemSelected(AdapterView<?> parent, 469 View view, int pos, long id) { 470 if (pos == mVideoRecordSize) return; 471 472 log("Setting video record size to " + ((TextView)view).getText()); 473 mVideoRecordSize = pos; 474 } 475 476 public void onNothingSelected(AdapterView<?> parent) { 477 478 } 479 }; 480 481 private AdapterView.OnItemSelectedListener mVideoFrameRateListener = 482 new AdapterView.OnItemSelectedListener() { 483 public void onItemSelected(AdapterView<?> parent, 484 View view, int pos, long id) { 485 if (pos == mVideoFrameRate) return; 486 487 log("Setting video frame rate to " + ((TextView)view).getText()); 488 mVideoFrameRate = pos; 489 } 490 491 public void onNothingSelected(AdapterView<?> parent) { 492 493 } 494 }; 495 496 private View.OnClickListener mRecordToggleListener = 497 new View.OnClickListener() { 498 public void onClick(View v) { 499 mPreviewToggle.setEnabled(false); 500 if (mState == CAMERA_PREVIEW) { 501 startRecording(); 502 } else if (mState == CAMERA_RECORD) { 503 stopRecording(false); 504 } else { 505 logE("Can't toggle recording in current state!"); 506 } 507 mPreviewToggle.setEnabled(true); 508 } 509 }; 510 511 private View.OnClickListener mRecordStabilizationToggleListener = 512 new View.OnClickListener() { 513 public void onClick(View v) { 514 boolean on = ((ToggleButton) v).isChecked(); 515 mParams.setVideoStabilization(on); 516 } 517 }; 518 519 private Camera.ShutterCallback mShutterCb = new Camera.ShutterCallback() { 520 public void onShutter() { 521 log("Shutter callback received"); 522 } 523 }; 524 525 private Camera.PictureCallback mRawCb = new Camera.PictureCallback() { 526 public void onPictureTaken(byte[] data, Camera camera) { 527 log("Raw callback received"); 528 } 529 }; 530 531 private Camera.PictureCallback mPostviewCb = new Camera.PictureCallback() { 532 public void onPictureTaken(byte[] data, Camera camera) { 533 log("Postview callback received"); 534 } 535 }; 536 537 private Camera.PictureCallback mJpegCb = new Camera.PictureCallback() { 538 public void onPictureTaken(byte[] data, Camera camera) { 539 log("JPEG picture callback received"); 540 FragmentManager fm = getFragmentManager(); 541 SnapshotDialogFragment snapshotDialog = new SnapshotDialogFragment(); 542 543 snapshotDialog.updateImage(data); 544 snapshotDialog.show(fm, "snapshot_dialog_fragment"); 545 546 mPreviewToggle.setEnabled(true); 547 548 mState = CAMERA_OPEN; 549 } 550 }; 551 552 // Internal methods 553 554 void setUpCamera() { 555 log("Setting up camera " + mCameraId); 556 logIndent(1); 557 if (mState >= CAMERA_OPEN) { 558 log("Closing old camera"); 559 mCamera.release(); 560 mState = CAMERA_UNINITIALIZED; 561 } 562 log("Opening camera " + mCameraId); 563 mCamera = Camera.open(mCameraId); 564 mState = CAMERA_OPEN; 565 566 mParams = mCamera.getParameters(); 567 568 // Set up preview size selection 569 570 log("Configuring camera"); 571 logIndent(1); 572 573 updatePreviewSizes(mParams); 574 updateAfModes(mParams); 575 updateFlashModes(mParams); 576 updateSnapshotSizes(mParams); 577 updateCamcorderProfile(mCameraId); 578 updateVideoRecordSize(mCameraId); 579 updateVideoFrameRate(mCameraId); 580 581 // Trigger updating video record size to match camcorder profile 582 mCamcorderProfileSpinner.setSelection(mCamcorderProfile); 583 584 if (mParams.isVideoStabilizationSupported()) { 585 log("Video stabilization is supported"); 586 mRecordStabilizationToggle.setEnabled(true); 587 } else { 588 log("Video stabilization not supported"); 589 mRecordStabilizationToggle.setEnabled(false); 590 } 591 592 if (mParams.isAutoExposureLockSupported()) { 593 log("Auto-Exposure locking is supported"); 594 mExposureLockToggle.setEnabled(true); 595 } else { 596 log("Auto-Exposure locking is not supported"); 597 mExposureLockToggle.setEnabled(false); 598 } 599 600 // Update parameters based on above updates 601 mCamera.setParameters(mParams); 602 603 if (mPreviewHolder != null) { 604 log("Setting preview display"); 605 try { 606 mCamera.setPreviewDisplay(mPreviewHolder); 607 } catch(IOException e) { 608 Log.e(TAG, "Unable to set up preview!"); 609 } 610 } 611 612 logIndent(-1); 613 614 mPreviewToggle.setEnabled(true); 615 mPreviewToggle.setChecked(false); 616 enablePreviewOnlyControls(false); 617 618 int width = mPreviewSizes.get(mPreviewSize).width; 619 int height = mPreviewSizes.get(mPreviewSize).height; 620 resizePreview(width, height); 621 if (mPreviewToggle.isChecked()) { 622 log("Starting preview" ); 623 mCamera.startPreview(); 624 mState = CAMERA_PREVIEW; 625 } else { 626 mState = CAMERA_OPEN; 627 } 628 logIndent(-1); 629 } 630 631 private void updateAfModes(Parameters params) { 632 mAfModes = params.getSupportedFocusModes(); 633 634 mAutofocusModeSpinner.setAdapter( 635 new ArrayAdapter<String>(this, R.layout.spinner_item, 636 mAfModes.toArray(new String[0]))); 637 638 mAfMode = 0; 639 640 params.setFocusMode(mAfModes.get(mAfMode)); 641 642 log("Setting AF mode to " + mAfModes.get(mAfMode)); 643 } 644 645 private void updateFlashModes(Parameters params) { 646 mFlashModes = params.getSupportedFlashModes(); 647 648 mFlashModeSpinner.setAdapter( 649 new ArrayAdapter<String>(this, R.layout.spinner_item, 650 mFlashModes.toArray(new String[0]))); 651 652 mFlashMode = 0; 653 654 params.setFlashMode(mFlashModes.get(mFlashMode)); 655 656 log("Setting Flash mode to " + mFlashModes.get(mFlashMode)); 657 } 658 659 private View.OnClickListener mExposureLockToggleListener = 660 new View.OnClickListener() { 661 public void onClick(View v) { 662 boolean on = ((ToggleButton) v).isChecked(); 663 log("Auto-Exposure was " + mParams.getAutoExposureLock()); 664 mParams.setAutoExposureLock(on); 665 log("Auto-Exposure is now " + mParams.getAutoExposureLock()); 666 } 667 }; 668 669 private void updatePreviewSizes(Camera.Parameters params) { 670 mPreviewSizes = params.getSupportedPreviewSizes(); 671 672 String[] availableSizeNames = new String[mPreviewSizes.size()]; 673 int i = 0; 674 for (Camera.Size previewSize: mPreviewSizes) { 675 availableSizeNames[i++] = 676 Integer.toString(previewSize.width) + " x " + 677 Integer.toString(previewSize.height); 678 } 679 mPreviewSizeSpinner.setAdapter( 680 new ArrayAdapter<String>( 681 this, R.layout.spinner_item, availableSizeNames)); 682 683 mPreviewSize = 0; 684 685 int width = mPreviewSizes.get(mPreviewSize).width; 686 int height = mPreviewSizes.get(mPreviewSize).height; 687 params.setPreviewSize(width, height); 688 log("Setting preview size to " + width + " x " + height); 689 } 690 691 private void updateSnapshotSizes(Camera.Parameters params) { 692 String[] availableSizeNames; 693 mSnapshotSizes = params.getSupportedPictureSizes(); 694 695 availableSizeNames = new String[mSnapshotSizes.size()]; 696 int i = 0; 697 for (Camera.Size snapshotSize : mSnapshotSizes) { 698 availableSizeNames[i++] = 699 Integer.toString(snapshotSize.width) + " x " + 700 Integer.toString(snapshotSize.height); 701 } 702 mSnapshotSizeSpinner.setAdapter( 703 new ArrayAdapter<String>( 704 this, R.layout.spinner_item, availableSizeNames)); 705 706 mSnapshotSize = 0; 707 708 int snapshotWidth = mSnapshotSizes.get(mSnapshotSize).width; 709 int snapshotHeight = mSnapshotSizes.get(mSnapshotSize).height; 710 params.setPictureSize(snapshotWidth, snapshotHeight); 711 log("Setting snapshot size to " + snapshotWidth + " x " + snapshotHeight); 712 } 713 714 private void updateCamcorderProfile(int cameraId) { 715 // Have to query all of these individually, 716 final int PROFILES[] = new int[] { 717 CamcorderProfile.QUALITY_1080P, 718 CamcorderProfile.QUALITY_480P, 719 CamcorderProfile.QUALITY_720P, 720 CamcorderProfile.QUALITY_CIF, 721 CamcorderProfile.QUALITY_HIGH, 722 CamcorderProfile.QUALITY_LOW, 723 CamcorderProfile.QUALITY_QCIF, 724 CamcorderProfile.QUALITY_QVGA, 725 CamcorderProfile.QUALITY_TIME_LAPSE_1080P, 726 CamcorderProfile.QUALITY_TIME_LAPSE_480P, 727 CamcorderProfile.QUALITY_TIME_LAPSE_720P, 728 CamcorderProfile.QUALITY_TIME_LAPSE_CIF, 729 CamcorderProfile.QUALITY_TIME_LAPSE_HIGH, 730 CamcorderProfile.QUALITY_TIME_LAPSE_LOW, 731 CamcorderProfile.QUALITY_TIME_LAPSE_QCIF, 732 CamcorderProfile.QUALITY_TIME_LAPSE_QVGA 733 }; 734 735 final String PROFILE_NAMES[] = new String[] { 736 "1080P", 737 "480P", 738 "720P", 739 "CIF", 740 "HIGH", 741 "LOW", 742 "QCIF", 743 "QVGA", 744 "TIME_LAPSE_1080P", 745 "TIME_LAPSE_480P", 746 "TIME_LAPSE_720P", 747 "TIME_LAPSE_CIF", 748 "TIME_LAPSE_HIGH", 749 "TIME_LAPSE_LOW", 750 "TIME_LAPSE_QCIF", 751 "TIME_LAPSE_QVGA" 752 }; 753 754 List<String> availableCamcorderProfileNames = new ArrayList<String>(); 755 mCamcorderProfiles = new ArrayList<CamcorderProfile>(); 756 757 for (int i = 0; i < PROFILES.length; i++) { 758 if (CamcorderProfile.hasProfile(cameraId, PROFILES[i])) { 759 availableCamcorderProfileNames.add(PROFILE_NAMES[i]); 760 mCamcorderProfiles.add(CamcorderProfile.get(cameraId, PROFILES[i])); 761 } 762 } 763 String[] nameArray = (String[])availableCamcorderProfileNames.toArray(new String[0]); 764 mCamcorderProfileSpinner.setAdapter( 765 new ArrayAdapter<String>( 766 this, R.layout.spinner_item, nameArray)); 767 768 mCamcorderProfile = 0; 769 log("Setting camcorder profile to " + nameArray[mCamcorderProfile]); 770 771 } 772 773 private void updateVideoRecordSize(int cameraId) { 774 List<Camera.Size> videoSizes = mParams.getSupportedVideoSizes(); 775 if (videoSizes == null) { // TODO: surface this to the user 776 log("Failed to get video size list, using preview sizes instead"); 777 videoSizes = mParams.getSupportedPreviewSizes(); 778 } 779 780 List<String> availableVideoRecordSizes = new ArrayList<String>(); 781 mVideoRecordSizes = new ArrayList<Camera.Size>(); 782 783 availableVideoRecordSizes.add("Default"); 784 mVideoRecordSizes.add(mCamera.new Size(0,0)); 785 786 for (Camera.Size s : videoSizes) { 787 availableVideoRecordSizes.add(s.width + "x" + s.height); 788 mVideoRecordSizes.add(s); 789 } 790 String[] nameArray = (String[])availableVideoRecordSizes.toArray(new String[0]); 791 mVideoRecordSizeSpinner.setAdapter( 792 new ArrayAdapter<String>( 793 this, R.layout.spinner_item, nameArray)); 794 795 mVideoRecordSize = 0; 796 log("Setting video record profile to " + nameArray[mVideoRecordSize]); 797 } 798 799 private void updateVideoFrameRate(int cameraId) { 800 // Use preview framerates as video framerates 801 List<Integer> frameRates = mParams.getSupportedPreviewFrameRates(); 802 803 List<String> frameRateStrings = new ArrayList<String>(); 804 mVideoFrameRates = new ArrayList<Integer>(); 805 806 frameRateStrings.add("Default"); 807 mVideoFrameRates.add(0); 808 809 for (Integer frameRate : frameRates) { 810 frameRateStrings.add(frameRate.toString()); 811 mVideoFrameRates.add(frameRate); 812 } 813 String[] nameArray = (String[])frameRateStrings.toArray(new String[0]); 814 mVideoFrameRateSpinner.setAdapter( 815 new ArrayAdapter<String>( 816 this, R.layout.spinner_item, nameArray)); 817 818 mVideoFrameRate = 0; 819 log("Setting frame rate to " + nameArray[mVideoFrameRate]); 820 } 821 822 void resizePreview(int width, int height) { 823 if (mPreviewHolder != null) { 824 int viewHeight = mPreviewView.getHeight(); 825 int viewWidth = (int)(((double)width)/height * viewHeight); 826 827 mPreviewView.setLayoutParams( 828 new LayoutParams(viewWidth, viewHeight)); 829 } 830 831 } 832 833 static final int MEDIA_TYPE_IMAGE = 0; 834 static final int MEDIA_TYPE_VIDEO = 1; 835 File getOutputMediaFile(int type){ 836 // To be safe, you should check that the SDCard is mounted 837 // using Environment.getExternalStorageState() before doing this. 838 839 String state = Environment.getExternalStorageState(); 840 if (!Environment.MEDIA_MOUNTED.equals(state)) { 841 return null; 842 } 843 844 File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory( 845 Environment.DIRECTORY_DCIM), "TestingCamera"); 846 // This location works best if you want the created images to be shared 847 // between applications and persist after your app has been uninstalled. 848 849 // Create the storage directory if it does not exist 850 if (! mediaStorageDir.exists()){ 851 if (! mediaStorageDir.mkdirs()){ 852 logE("Failed to create directory for pictures/video"); 853 return null; 854 } 855 } 856 857 // Create a media file name 858 String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); 859 File mediaFile; 860 if (type == MEDIA_TYPE_IMAGE){ 861 mediaFile = new File(mediaStorageDir.getPath() + File.separator + 862 "IMG_"+ timeStamp + ".jpg"); 863 } else if(type == MEDIA_TYPE_VIDEO) { 864 mediaFile = new File(mediaStorageDir.getPath() + File.separator + 865 "VID_"+ timeStamp + ".mp4"); 866 } else { 867 return null; 868 } 869 870 return mediaFile; 871 } 872 873 void notifyMediaScannerOfFile(File newFile, 874 final MediaScannerConnection.OnScanCompletedListener listener) { 875 final Handler h = new Handler(); 876 MediaScannerConnection.scanFile(this, 877 new String[] { newFile.toString() }, 878 null, 879 new MediaScannerConnection.OnScanCompletedListener() { 880 public void onScanCompleted(final String path, final Uri uri) { 881 h.post(new Runnable() { 882 public void run() { 883 log("MediaScanner notified: " + 884 path + " -> " + uri); 885 if (listener != null) 886 listener.onScanCompleted(path, uri); 887 } 888 }); 889 } 890 }); 891 } 892 893 private void deleteFile(File badFile) { 894 if (badFile.exists()) { 895 boolean success = badFile.delete(); 896 if (success) log("Deleted file " + badFile.toString()); 897 else log("Unable to delete file " + badFile.toString()); 898 } 899 } 900 901 private void startRecording() { 902 log("Starting recording"); 903 logIndent(1); 904 log("Configuring MediaRecoder"); 905 mCamera.unlock(); 906 if (mRecorder != null) { 907 mRecorder.release(); 908 } 909 mRecorder = new MediaRecorder(); 910 mRecorder.setOnErrorListener(mRecordingErrorListener); 911 mRecorder.setOnInfoListener(mRecordingInfoListener); 912 mRecorder.setCamera(mCamera); 913 mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); 914 mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 915 mRecorder.setProfile(mCamcorderProfiles.get(mCamcorderProfile)); 916 Camera.Size videoRecordSize = mVideoRecordSizes.get(mVideoRecordSize); 917 if (videoRecordSize.width > 0 && videoRecordSize.height > 0) { 918 mRecorder.setVideoSize(videoRecordSize.width, videoRecordSize.height); 919 } 920 if (mVideoFrameRates.get(mVideoFrameRate) > 0) { 921 mRecorder.setVideoFrameRate(mVideoFrameRates.get(mVideoFrameRate)); 922 } 923 File outputFile = getOutputMediaFile(MEDIA_TYPE_VIDEO); 924 log("File name:" + outputFile.toString()); 925 mRecorder.setOutputFile(outputFile.toString()); 926 927 boolean ready = false; 928 log("Preparing MediaRecorder"); 929 try { 930 mRecorder.prepare(); 931 ready = true; 932 } catch (Exception e) { 933 StringWriter writer = new StringWriter(); 934 e.printStackTrace(new PrintWriter(writer)); 935 logE("Exception preparing MediaRecorder:\n" + writer.toString()); 936 } 937 938 if (ready) { 939 try { 940 log("Starting MediaRecorder"); 941 mRecorder.start(); 942 mState = CAMERA_RECORD; 943 log("Recording active"); 944 mRecordingFile = outputFile; 945 } catch (Exception e) { 946 StringWriter writer = new StringWriter(); 947 e.printStackTrace(new PrintWriter(writer)); 948 logE("Exception starting MediaRecorder:\n" + writer.toString()); 949 } 950 } else { 951 mPreviewToggle.setChecked(false); 952 } 953 logIndent(-1); 954 } 955 956 private MediaRecorder.OnErrorListener mRecordingErrorListener = 957 new MediaRecorder.OnErrorListener() { 958 public void onError(MediaRecorder mr, int what, int extra) { 959 logE("MediaRecorder reports error: " + what + ", extra " 960 + extra); 961 if (mState == CAMERA_RECORD) { 962 stopRecording(true); 963 } 964 } 965 }; 966 967 private MediaRecorder.OnInfoListener mRecordingInfoListener = 968 new MediaRecorder.OnInfoListener() { 969 public void onInfo(MediaRecorder mr, int what, int extra) { 970 log("MediaRecorder reports info: " + what + ", extra " 971 + extra); 972 } 973 }; 974 975 private void stopRecording(boolean error) { 976 log("Stopping recording"); 977 if (mRecorder != null) { 978 mRecorder.stop(); 979 mCamera.lock(); 980 mState = CAMERA_PREVIEW; 981 if (!error) { 982 notifyMediaScannerOfFile(mRecordingFile, null); 983 } else { 984 deleteFile(mRecordingFile); 985 } 986 mRecordingFile = null; 987 } else { 988 logE("Recorder is unexpectedly null!"); 989 } 990 } 991 992 private int mLogIndentLevel = 0; 993 private String mLogIndent = "\t"; 994 /** Increment or decrement log indentation level */ 995 synchronized void logIndent(int delta) { 996 mLogIndentLevel += delta; 997 if (mLogIndentLevel < 0) mLogIndentLevel = 0; 998 char[] mLogIndentArray = new char[mLogIndentLevel + 1]; 999 for (int i = -1; i < mLogIndentLevel; i++) { 1000 mLogIndentArray[i + 1] = '\t'; 1001 } 1002 mLogIndent = new String(mLogIndentArray); 1003 } 1004 1005 SimpleDateFormat mDateFormatter = new SimpleDateFormat("HH:mm:ss.SSS"); 1006 /** Log both to log text view and to device logcat */ 1007 void log(String logLine) { 1008 Log.d(TAG, logLine); 1009 logAndScrollToBottom(logLine, mLogIndent); 1010 } 1011 1012 void logE(String logLine) { 1013 Log.e(TAG, logLine); 1014 logAndScrollToBottom(logLine, mLogIndent + "!!! "); 1015 } 1016 1017 synchronized private void logAndScrollToBottom(String logLine, String logIndent) { 1018 StringBuffer logEntry = new StringBuffer(32); 1019 logEntry.append("\n").append(mDateFormatter.format(new Date())).append(logIndent); 1020 logEntry.append(logLine); 1021 mLogView.append(logEntry); 1022 final Layout layout = mLogView.getLayout(); 1023 if (layout != null){ 1024 int scrollDelta = layout.getLineBottom(mLogView.getLineCount() - 1) 1025 - mLogView.getScrollY() - mLogView.getHeight(); 1026 if(scrollDelta > 0) { 1027 mLogView.scrollBy(0, scrollDelta); 1028 } 1029 } 1030 } 1031 1032} 1033