PanoramaActivity.java revision f26e1ab12cb3f359fe1d6efcbe65344a3a0bf276
1/* 2 * Copyright (C) 2011 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.camera.panorama; 18 19import com.android.camera.CameraDisabledException; 20import com.android.camera.CameraHardwareException; 21import com.android.camera.CameraHolder; 22import com.android.camera.MenuHelper; 23import com.android.camera.ModePicker; 24import com.android.camera.R; 25import com.android.camera.Storage; 26import com.android.camera.Util; 27 28import android.app.Activity; 29import android.app.ProgressDialog; 30import android.content.Context; 31import android.graphics.Bitmap; 32import android.graphics.BitmapFactory; 33import android.graphics.ImageFormat; 34import android.graphics.PixelFormat; 35import android.graphics.Rect; 36import android.graphics.SurfaceTexture; 37import android.graphics.YuvImage; 38import android.hardware.Camera; 39import android.hardware.Camera.Parameters; 40import android.hardware.Camera.Size; 41import android.hardware.Sensor; 42import android.hardware.SensorEvent; 43import android.hardware.SensorEventListener; 44import android.hardware.SensorManager; 45import android.os.Bundle; 46import android.os.Handler; 47import android.os.Message; 48import android.util.Log; 49import android.view.View; 50import android.view.WindowManager; 51import android.view.animation.Animation; 52import android.view.animation.AnimationUtils; 53import android.widget.Button; 54import android.widget.ImageView; 55import android.widget.TextView; 56 57import java.io.ByteArrayOutputStream; 58import java.util.List; 59 60/** 61 * Activity to handle panorama capturing. 62 */ 63public class PanoramaActivity extends Activity implements 64 ModePicker.OnModeChangeListener, 65 SurfaceTexture.OnFrameAvailableListener, 66 MosaicRendererSurfaceViewRenderer.MosaicSurfaceCreateListener { 67 public static final int DEFAULT_SWEEP_ANGLE = 160; 68 public static final int DEFAULT_BLEND_MODE = Mosaic.BLENDTYPE_HORIZONTAL; 69 public static final int DEFAULT_CAPTURE_PIXELS = 960 * 720; 70 71 private static final int MSG_FINAL_MOSAIC_READY = 1; 72 private static final int MSG_RESET_TO_PREVIEW = 2; 73 74 private static final String TAG = "PanoramaActivity"; 75 private static final int PREVIEW_STOPPED = 0; 76 private static final int PREVIEW_ACTIVE = 1; 77 private static final int CAPTURE_VIEWFINDER = 0; 78 private static final int CAPTURE_MOSAIC = 1; 79 80 // Speed is in unit of deg/sec 81 private static final float PANNING_SPEED_THRESHOLD = 30f; 82 83 // Ratio of nanosecond to second 84 private static final float NS2S = 1.0f / 1000000000.0f; 85 86 private boolean mPausing; 87 88 private View mPanoControlLayout; 89 private View mCaptureLayout; 90 private Button mStopCaptureButton; 91 private View mReviewLayout; 92 private ImageView mReview; 93 private CaptureView mCaptureView; 94 private MosaicRendererSurfaceView mMosaicView; 95 private TextView mTooFastPrompt; 96 private Animation mSlideIn, mSlideOut; 97 98 private ProgressDialog mProgressDialog; 99 private String mPreparePreviewString; 100 private String mGeneratePanoramaString; 101 102 private int mPreviewWidth; 103 private int mPreviewHeight; 104 private Camera mCameraDevice; 105 private int mCameraState; 106 private int mCaptureState; 107 private SensorManager mSensorManager; 108 private Sensor mSensor; 109 private ModePicker mModePicker; 110 private MosaicFrameProcessor mMosaicFrameProcessor; 111 private String mCurrentImagePath = null; 112 private long mTimeTaken; 113 private Handler mMainHandler; 114 private SurfaceTexture mSurfaceTexture; 115 private boolean mThreadRunning; 116 private float[] mTransformMatrix; 117 private float mHorizontalViewAngle; 118 private float mVerticalViewAngle; 119 120 @Override 121 public void onCreate(Bundle icicle) { 122 super.onCreate(icicle); 123 124 getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, 125 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 126 127 createContentView(); 128 129 mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); 130 mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); 131 if (mSensor == null) { 132 mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION); 133 } 134 135 mTransformMatrix = new float[16]; 136 137 mPreparePreviewString = 138 getResources().getString(R.string.pano_dialog_prepare_preview); 139 mGeneratePanoramaString = 140 getResources().getString(R.string.pano_dialog_generate_panorama); 141 142 Context context = getApplicationContext(); 143 mSlideIn = AnimationUtils.loadAnimation(context, R.anim.slide_in_from_right); 144 mSlideOut = AnimationUtils.loadAnimation(context, R.anim.slide_out_to_right); 145 146 mMainHandler = new Handler() { 147 @Override 148 public void handleMessage(Message msg) { 149 switch (msg.what) { 150 case MSG_FINAL_MOSAIC_READY: 151 onBackgroundThreadFinished(); 152 showFinalMosaic((Bitmap) msg.obj); 153 break; 154 case MSG_RESET_TO_PREVIEW: 155 onBackgroundThreadFinished(); 156 resetToPreview(); 157 break; 158 } 159 clearMosaicFrameProcessorIfNeeded(); 160 } 161 }; 162 } 163 164 private void setupCamera() { 165 openCamera(); 166 Parameters parameters = mCameraDevice.getParameters(); 167 setupCaptureParams(parameters); 168 configureCamera(parameters); 169 } 170 171 private void releaseCamera() { 172 if (mCameraDevice != null) { 173 mCameraDevice.setPreviewCallbackWithBuffer(null); 174 CameraHolder.instance().release(); 175 mCameraDevice = null; 176 mCameraState = PREVIEW_STOPPED; 177 } 178 } 179 180 private void openCamera() { 181 try { 182 mCameraDevice = Util.openCamera(this, CameraHolder.instance().getBackCameraId()); 183 } catch (CameraHardwareException e) { 184 Util.showErrorAndFinish(this, R.string.cannot_connect_camera); 185 return; 186 } catch (CameraDisabledException e) { 187 Util.showErrorAndFinish(this, R.string.camera_disabled); 188 return; 189 } 190 } 191 192 private boolean findBestPreviewSize(List<Size> supportedSizes, boolean need4To3, 193 boolean needSmaller) { 194 int pixelsDiff = DEFAULT_CAPTURE_PIXELS; 195 boolean hasFound = false; 196 for (Size size : supportedSizes) { 197 int h = size.height; 198 int w = size.width; 199 // we only want 4:3 format. 200 int d = DEFAULT_CAPTURE_PIXELS - h * w; 201 if (needSmaller && d < 0) { // no bigger preview than 960x720. 202 continue; 203 } 204 if (need4To3 && (h * 4 != w * 3)) { 205 continue; 206 } 207 d = Math.abs(d); 208 if (d < pixelsDiff) { 209 mPreviewWidth = w; 210 mPreviewHeight = h; 211 pixelsDiff = d; 212 hasFound = true; 213 } 214 } 215 return hasFound; 216 } 217 218 private void setupCaptureParams(Parameters parameters) { 219 List<Size> supportedSizes = parameters.getSupportedPreviewSizes(); 220 if (!findBestPreviewSize(supportedSizes, true, true)) { 221 Log.w(TAG, "No 4:3 ratio preview size supported."); 222 if (!findBestPreviewSize(supportedSizes, false, true)) { 223 Log.w(TAG, "Can't find a supported preview size smaller than 960x720."); 224 findBestPreviewSize(supportedSizes, false, false); 225 } 226 } 227 Log.v(TAG, "preview h = " + mPreviewHeight + " , w = " + mPreviewWidth); 228 parameters.setPreviewSize(mPreviewWidth, mPreviewHeight); 229 230 List<int[]> frameRates = parameters.getSupportedPreviewFpsRange(); 231 int last = frameRates.size() - 1; 232 int minFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MIN_INDEX]; 233 int maxFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MAX_INDEX]; 234 parameters.setPreviewFpsRange(minFps, maxFps); 235 Log.v(TAG, "preview fps: " + minFps + ", " + maxFps); 236 237 parameters.setRecordingHint(false); 238 239 mHorizontalViewAngle = parameters.getHorizontalViewAngle(); 240 mVerticalViewAngle = parameters.getVerticalViewAngle(); 241 } 242 243 public int getPreviewBufSize() { 244 PixelFormat pixelInfo = new PixelFormat(); 245 PixelFormat.getPixelFormatInfo(mCameraDevice.getParameters().getPreviewFormat(), pixelInfo); 246 // TODO: remove this extra 32 byte after the driver bug is fixed. 247 return (mPreviewWidth * mPreviewHeight * pixelInfo.bitsPerPixel / 8) + 32; 248 } 249 250 private void configureCamera(Parameters parameters) { 251 mCameraDevice.setParameters(parameters); 252 253 int orientation = Util.getDisplayOrientation(Util.getDisplayRotation(this), 254 CameraHolder.instance().getBackCameraId()); 255 mCameraDevice.setDisplayOrientation(orientation); 256 } 257 258 private boolean switchToOtherMode(int mode) { 259 if (isFinishing()) { 260 return false; 261 } 262 MenuHelper.gotoMode(mode, this); 263 finish(); 264 return true; 265 } 266 267 public boolean onModeChanged(int mode) { 268 if (mode != ModePicker.MODE_PANORAMA) { 269 return switchToOtherMode(mode); 270 } else { 271 return true; 272 } 273 } 274 275 @Override 276 public void onMosaicSurfaceCreated(final int textureID) { 277 runOnUiThread(new Runnable() { 278 @Override 279 public void run() { 280 if (mSurfaceTexture != null) { 281 mSurfaceTexture.release(); 282 } 283 mSurfaceTexture = new SurfaceTexture(textureID); 284 if (!mPausing) { 285 mSurfaceTexture.setOnFrameAvailableListener(PanoramaActivity.this); 286 startCameraPreview(); 287 } 288 } 289 }); 290 } 291 292 public void runViewFinder() { 293 mMosaicView.setWarping(false); 294 // Call preprocess to render it to low-res and high-res RGB textures. 295 mMosaicView.preprocess(mTransformMatrix); 296 mMosaicView.setReady(); 297 mMosaicView.requestRender(); 298 } 299 300 public void runMosaicCapture() { 301 mMosaicView.setWarping(true); 302 // Call preprocess to render it to low-res and high-res RGB textures. 303 mMosaicView.preprocess(mTransformMatrix); 304 // Lock the conditional variable to ensure the order of transferGPUtoCPU and 305 // mMosaicFrame.processFrame(). 306 mMosaicView.lockPreviewReadyFlag(); 307 // Now, transfer the textures from GPU to CPU memory for processing 308 mMosaicView.transferGPUtoCPU(); 309 // Wait on the condition variable (will be opened when GPU->CPU transfer is done). 310 mMosaicView.waitUntilPreviewReady(); 311 mMosaicFrameProcessor.processFrame(); 312 } 313 314 public synchronized void onFrameAvailable(SurfaceTexture surface) { 315 /* This function may be called by some random thread, 316 * so let's be safe and use synchronize. No OpenGL calls can be done here. 317 */ 318 // Updating the texture should be done in the GL thread which mMosaicView is attached. 319 mMosaicView.queueEvent(new Runnable() { 320 @Override 321 public void run() { 322 mSurfaceTexture.updateTexImage(); 323 mSurfaceTexture.getTransformMatrix(mTransformMatrix); 324 } 325 }); 326 // Update the transformation matrix for mosaic pre-process. 327 if (mCaptureState == CAPTURE_VIEWFINDER) { 328 runViewFinder(); 329 } else { 330 runMosaicCapture(); 331 } 332 } 333 334 public void startCapture() { 335 // Reset values so we can do this again. 336 mTimeTaken = System.currentTimeMillis(); 337 mCaptureState = CAPTURE_MOSAIC; 338 mTooFastPrompt.setVisibility(View.GONE); 339 340 mMosaicFrameProcessor.setProgressListener(new MosaicFrameProcessor.ProgressListener() { 341 @Override 342 public void onProgress(boolean isFinished, float panningRateX, float panningRateY, 343 int traversedAngleX, int traversedAngleY) { 344 if (isFinished) { 345 stopCapture(); 346 } else { 347 updateProgress(panningRateX, panningRateY, traversedAngleX, traversedAngleY); 348 } 349 } 350 }); 351 352 mStopCaptureButton.setVisibility(View.VISIBLE); 353 mCaptureView.setVisibility(View.VISIBLE); 354 mMosaicView.setVisibility(View.VISIBLE); 355 mPanoControlLayout.startAnimation(mSlideOut); 356 mPanoControlLayout.setVisibility(View.GONE); 357 358 } 359 360 private void stopCapture() { 361 mCaptureState = CAPTURE_VIEWFINDER; 362 363 mMosaicFrameProcessor.setProgressListener(null); 364 stopCameraPreview(); 365 366 mSurfaceTexture.setOnFrameAvailableListener(null); 367 368 // TODO: show some dialog for long computation. 369 if (!mThreadRunning) { 370 runBackgroundThread(mPreparePreviewString, new Thread() { 371 @Override 372 public void run() { 373 byte[] jpegData = generateFinalMosaic(false); 374 Bitmap bitmap = null; 375 if (jpegData != null) { 376 bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length); 377 } 378 mMainHandler.sendMessage(mMainHandler.obtainMessage( 379 MSG_FINAL_MOSAIC_READY, bitmap)); 380 } 381 }); 382 } 383 } 384 385 private void updateProgress(float panningRateX, float panningRateY, 386 int traversedAngleX, int traversedAngleY) { 387 388 mMosaicView.setReady(); 389 mMosaicView.requestRender(); 390 391 // TODO: Now we just display warning message by the panning speed. 392 // Since we only support horizontal panning, we should display a warning message 393 // in UI when there're significant vertical movements. 394 if ((panningRateX * mHorizontalViewAngle > PANNING_SPEED_THRESHOLD) 395 || (panningRateY * mVerticalViewAngle > PANNING_SPEED_THRESHOLD)) { 396 // TODO: draw speed indication according to the UI spec. 397 mTooFastPrompt.setVisibility(View.VISIBLE); 398 mCaptureView.setSweepAngle(Math.max(traversedAngleX, traversedAngleY) + 1); 399 mCaptureView.invalidate(); 400 } else { 401 mTooFastPrompt.setVisibility(View.GONE); 402 mCaptureView.setSweepAngle(Math.max(traversedAngleX, traversedAngleY) + 1); 403 mCaptureView.invalidate(); 404 } 405 } 406 407 private void createContentView() { 408 setContentView(R.layout.panorama); 409 410 mCaptureState = CAPTURE_VIEWFINDER; 411 412 mCaptureLayout = (View) findViewById(R.id.pano_capture_layout); 413 mCaptureView = (CaptureView) findViewById(R.id.pano_capture_view); 414 mCaptureView.setStartAngle(-DEFAULT_SWEEP_ANGLE / 2); 415 mStopCaptureButton = (Button) findViewById(R.id.pano_capture_stop_button); 416 mTooFastPrompt = (TextView) findViewById(R.id.pano_capture_too_fast_textview); 417 418 mReviewLayout = (View) findViewById(R.id.pano_review_layout); 419 mReview = (ImageView) findViewById(R.id.pano_reviewarea); 420 mMosaicView = (MosaicRendererSurfaceView) findViewById(R.id.pano_renderer); 421 mMosaicView.getRenderer().setMosaicSurfaceCreateListener(this); 422 mMosaicView.setVisibility(View.VISIBLE); 423 424 mPanoControlLayout = (View) findViewById(R.id.pano_control_layout); 425 mModePicker = (ModePicker) findViewById(R.id.mode_picker); 426 mModePicker.setVisibility(View.VISIBLE); 427 mModePicker.setOnModeChangeListener(this); 428 mModePicker.setCurrentMode(ModePicker.MODE_PANORAMA); 429 } 430 431 @OnClickAttr 432 public void onStartButtonClicked(View v) { 433 // If mSurfaceTexture == null then GL setup is not finished yet. 434 // No buttons can be pressed. 435 if (mPausing || mThreadRunning || mSurfaceTexture == null) return; 436 startCapture(); 437 } 438 439 @OnClickAttr 440 public void onStopButtonClicked(View v) { 441 if (mPausing || mThreadRunning || mSurfaceTexture == null) return; 442 stopCapture(); 443 } 444 445 @OnClickAttr 446 public void onOkButtonClicked(View v) { 447 if (mPausing || mThreadRunning || mSurfaceTexture == null) return; 448 runBackgroundThread(mGeneratePanoramaString, new Thread() { 449 @Override 450 public void run() { 451 byte[] jpegData = generateFinalMosaic(true); 452 savePanorama(jpegData); 453 mMainHandler.sendMessage(mMainHandler.obtainMessage(MSG_RESET_TO_PREVIEW)); 454 } 455 }); 456 } 457 458 private void runBackgroundThread(String str, Thread thread) { 459 mThreadRunning = true; 460 mProgressDialog = ProgressDialog.show(this, "", str); 461 thread.start(); 462 } 463 464 private void onBackgroundThreadFinished() { 465 mThreadRunning = false; 466 mProgressDialog.dismiss(); 467 mProgressDialog = null; 468 } 469 470 @OnClickAttr 471 public void onRetakeButtonClicked(View v) { 472 if (mPausing || mThreadRunning || mSurfaceTexture == null) return; 473 resetToPreview(); 474 } 475 476 private void resetToPreview() { 477 mCaptureState = CAPTURE_VIEWFINDER; 478 479 mReviewLayout.setVisibility(View.GONE); 480 mStopCaptureButton.setVisibility(View.GONE); 481 mCaptureView.setVisibility(View.GONE); 482 mPanoControlLayout.setVisibility(View.VISIBLE); 483 mPanoControlLayout.startAnimation(mSlideIn); 484 mCaptureLayout.setVisibility(View.VISIBLE); 485 mMosaicFrameProcessor.reset(); 486 487 mSurfaceTexture.setOnFrameAvailableListener(this); 488 489 if (!mPausing) startCameraPreview(); 490 491 mMosaicView.setVisibility(View.VISIBLE); 492 } 493 494 private void showFinalMosaic(Bitmap bitmap) { 495 if (bitmap != null) { 496 mReview.setImageBitmap(bitmap); 497 } 498 mCaptureLayout.setVisibility(View.GONE); 499 mReviewLayout.setVisibility(View.VISIBLE); 500 mCaptureView.setSweepAngle(0); 501 } 502 503 private void savePanorama(byte[] jpegData) { 504 if (jpegData != null) { 505 String imagePath = PanoUtil.createName( 506 getResources().getString(R.string.pano_file_name_format), mTimeTaken); 507 Storage.addImage(getContentResolver(), imagePath, mTimeTaken, null, 0, 508 jpegData); 509 } 510 } 511 512 private void clearMosaicFrameProcessorIfNeeded() { 513 if (!mPausing || mThreadRunning) return; 514 mMosaicFrameProcessor.clear(); 515 } 516 517 private void initMosaicFrameProcessorIfNeeded() { 518 if (mPausing || mThreadRunning) return; 519 if (mMosaicFrameProcessor == null) { 520 // Start the activity for the first time. 521 mMosaicFrameProcessor = new MosaicFrameProcessor(DEFAULT_SWEEP_ANGLE - 5, 522 mPreviewWidth, mPreviewHeight, getPreviewBufSize()); 523 } 524 mMosaicFrameProcessor.initialize(); 525 } 526 527 @Override 528 protected void onPause() { 529 super.onPause(); 530 531 releaseCamera(); 532 mPausing = true; 533 mMosaicView.onPause(); 534 mSensorManager.unregisterListener(mListener); 535 clearMosaicFrameProcessorIfNeeded(); 536 System.gc(); 537 } 538 539 @Override 540 protected void onResume() { 541 super.onResume(); 542 543 mPausing = false; 544 /* 545 * It is not necessary to get accelerometer events at a very high rate, 546 * by using a slower rate (SENSOR_DELAY_UI), we get an automatic 547 * low-pass filter, which "extracts" the gravity component of the 548 * acceleration. As an added benefit, we use less power and CPU 549 * resources. 550 */ 551 mSensorManager.registerListener(mListener, mSensor, SensorManager.SENSOR_DELAY_UI); 552 mCaptureState = CAPTURE_VIEWFINDER; 553 setupCamera(); 554 if (mSurfaceTexture != null) { 555 mSurfaceTexture.setOnFrameAvailableListener(this); 556 startCameraPreview(); 557 } 558 // Camera must be initialized before MosaicFrameProcessor is initialized. The preview size 559 // has to be decided by camera device. 560 initMosaicFrameProcessorIfNeeded(); 561 mMosaicView.onResume(); 562 } 563 564 private final SensorEventListener mListener = new SensorEventListener() { 565 private float mCompassCurrX; // degrees 566 private float mCompassCurrY; // degrees 567 private float mTimestamp; 568 569 public void onSensorChanged(SensorEvent event) { 570 if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) { 571 if (mTimestamp != 0) { 572 final float dT = (event.timestamp - mTimestamp) * NS2S; 573 mCompassCurrX += event.values[1] * dT * 180.0f / Math.PI; 574 mCompassCurrY += event.values[0] * dT * 180.0f / Math.PI; 575 } 576 mTimestamp = event.timestamp; 577 578 } else if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) { 579 mCompassCurrX = event.values[0]; 580 mCompassCurrY = event.values[1]; 581 } 582 583 if (mMosaicFrameProcessor != null) { 584 mMosaicFrameProcessor.updateCompassValue(mCompassCurrX, mCompassCurrY); 585 } 586 } 587 588 @Override 589 public void onAccuracyChanged(Sensor sensor, int accuracy) { 590 } 591 }; 592 593 public byte[] generateFinalMosaic(boolean highRes) { 594 mMosaicFrameProcessor.createMosaic(highRes); 595 596 byte[] imageData = mMosaicFrameProcessor.getFinalMosaicNV21(); 597 if (imageData == null) { 598 Log.e(TAG, "getFinalMosaicNV21() returned null."); 599 return null; 600 } 601 602 int len = imageData.length - 8; 603 int width = (imageData[len + 0] << 24) + ((imageData[len + 1] & 0xFF) << 16) 604 + ((imageData[len + 2] & 0xFF) << 8) + (imageData[len + 3] & 0xFF); 605 int height = (imageData[len + 4] << 24) + ((imageData[len + 5] & 0xFF) << 16) 606 + ((imageData[len + 6] & 0xFF) << 8) + (imageData[len + 7] & 0xFF); 607 Log.v(TAG, "ImLength = " + (len) + ", W = " + width + ", H = " + height); 608 609 if (width <= 0 || height <= 0) { 610 // TODO: pop up a error meesage indicating that the final result is not generated. 611 Log.e(TAG, "width|height <= 0!!, len = " + (len) + ", W = " + width + ", H = " + 612 height); 613 return null; 614 } 615 616 YuvImage yuvimage = new YuvImage(imageData, ImageFormat.NV21, width, height, null); 617 ByteArrayOutputStream out = new ByteArrayOutputStream(); 618 yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, out); 619 try { 620 out.close(); 621 } catch (Exception e) { 622 Log.e(TAG, "Exception in storing final mosaic", e); 623 return null; 624 } 625 return out.toByteArray(); 626 } 627 628 private void setPreviewTexture(SurfaceTexture surface) { 629 try { 630 mCameraDevice.setPreviewTexture(surface); 631 } catch (Throwable ex) { 632 releaseCamera(); 633 throw new RuntimeException("setPreviewTexture failed", ex); 634 } 635 } 636 637 private void startCameraPreview() { 638 // If we're previewing already, stop the preview first (this will blank 639 // the screen). 640 if (mCameraState != PREVIEW_STOPPED) stopCameraPreview(); 641 642 setPreviewTexture(mSurfaceTexture); 643 644 try { 645 Log.v(TAG, "startPreview"); 646 mCameraDevice.startPreview(); 647 } catch (Throwable ex) { 648 releaseCamera(); 649 throw new RuntimeException("startPreview failed", ex); 650 } 651 mCameraState = PREVIEW_ACTIVE; 652 } 653 654 private void stopCameraPreview() { 655 if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) { 656 Log.v(TAG, "stopPreview"); 657 mCameraDevice.stopPreview(); 658 } 659 mCameraState = PREVIEW_STOPPED; 660 } 661} 662