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