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