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