1 /* 2 * Copyright (C) 2007 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.cts.verifier.sensors; 18 19 import android.app.Activity; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.hardware.Camera; 23 import android.hardware.Sensor; 24 import android.hardware.SensorEvent; 25 import android.hardware.SensorEventListener; 26 import android.hardware.SensorManager; 27 import android.media.AudioManager; 28 import android.media.CamcorderProfile; 29 import android.media.MediaRecorder; 30 import android.media.SoundPool; 31 import android.net.Uri; 32 import android.os.Bundle; 33 import android.util.JsonWriter; 34 import android.util.Log; 35 import android.view.Surface; 36 import android.view.Window; 37 import android.view.WindowManager; 38 import android.widget.ImageView; 39 import android.widget.Toast; 40 41 import com.android.cts.verifier.R; 42 43 import java.io.File; 44 import java.io.FileNotFoundException; 45 import java.io.FileOutputStream; 46 import java.io.IOException; 47 import java.io.OutputStreamWriter; 48 import java.text.SimpleDateFormat; 49 import java.util.Date; 50 import java.util.HashMap; 51 import java.util.List; 52 import java.util.Map; 53 54 // ---------------------------------------------------------------------- 55 56 /** 57 * An activity that does recording of the camera video and rotation vector data at the same time. 58 */ 59 public class RVCVRecordActivity extends Activity { 60 private static final String TAG = "RVCVRecordActivity"; 61 private static final boolean LOCAL_LOGV = false; 62 63 private MotionIndicatorView mIndicatorView; 64 65 private SoundPool mSoundPool; 66 private Map<String, Integer> mSoundMap; 67 68 private File mRecordDir; 69 private RecordProcedureController mController; 70 private VideoRecorder mVideoRecorder; 71 private RVSensorLogger mRVSensorLogger; 72 private CoverageManager mCoverManager; 73 private CameraContext mCameraContext; 74 private int mDeviceRotation = Surface.ROTATION_0; 75 76 public static final int AXIS_NONE = 0; 77 public static final int AXIS_ALL = SensorManager.AXIS_X + 78 SensorManager.AXIS_Y + 79 SensorManager.AXIS_Z; 80 81 // For Rotation Vector algorithm research use 82 private final static boolean LOG_RAW_SENSORS = false; 83 private RawSensorLogger mRawSensorLogger; 84 85 public final RecordProcedureControllerCallback mRecordProcedureControllerCallback = 86 new RecordProcedureControllerCallback() { 87 public void startRecordProcedureController() { 88 startRecordcontroller(); 89 } 90 public void stopRecordProcedureController() { 91 stopRecordcontroller(); 92 } 93 }; 94 startRecordcontroller()95 public void startRecordcontroller() { 96 if (mController != null) { 97 Log.v(TAG, "startRecordcontroller is working. stop it"); 98 mController.quit(); 99 } 100 Log.v(TAG, "startRecordcontroller"); 101 mController = new RecordProcedureController(this); 102 } 103 stopRecordcontroller()104 public void stopRecordcontroller() { 105 if (mController != null) { 106 Log.v(TAG, "startRecordcontroller is working. stop it"); 107 mController.quit(); 108 } 109 Log.v(TAG, "stopRecordcontroller"); 110 } 111 112 @Override onCreate(Bundle savedInstanceState)113 public void onCreate(Bundle savedInstanceState) { 114 super.onCreate(savedInstanceState); 115 116 // Hide the window title. 117 requestWindowFeature(Window.FEATURE_NO_TITLE); 118 119 // inflate xml 120 setContentView(R.layout.cam_preview_overlay); 121 122 // locate views 123 mIndicatorView = (MotionIndicatorView) findViewById(R.id.cam_indicator); 124 WindowManager windowManager = 125 (WindowManager)getSystemService(Context.WINDOW_SERVICE); 126 if (windowManager != null) { 127 mDeviceRotation = windowManager.getDefaultDisplay().getRotation(); 128 mIndicatorView.setDeviceRotation(mDeviceRotation); 129 } 130 131 initStoragePath(); 132 } 133 134 @Override onPause()135 protected void onPause() { 136 super.onPause(); 137 if (mController != null) { 138 mController.quit(); 139 } 140 141 mCameraContext.end(); 142 endSoundPool(); 143 } 144 145 @Override onResume()146 protected void onResume() { 147 super.onResume(); 148 // delay the initialization as much as possible 149 init(); 150 } 151 152 /** display toast message 153 * 154 * @param msg Message content 155 */ message(String msg)156 private void message(String msg) { 157 158 Context context = getApplicationContext(); 159 int duration = Toast.LENGTH_SHORT; 160 161 Toast toast = Toast.makeText(context, msg, duration); 162 toast.show(); 163 } 164 165 /** 166 * Initialize components 167 * 168 */ init()169 private void init() { 170 mCameraContext = new CameraContext(); 171 mCameraContext.init(mRecordProcedureControllerCallback); 172 173 mCoverManager = new CoverageManager(); 174 mIndicatorView.setDataProvider( 175 mCoverManager.getAxis(SensorManager.AXIS_X), 176 mCoverManager.getAxis(SensorManager.AXIS_Y), 177 mCoverManager.getAxis(SensorManager.AXIS_Z) ); 178 179 initSoundPool(); 180 mRVSensorLogger = new RVSensorLogger(this); 181 182 mVideoRecorder = new VideoRecorder(mCameraContext.getCamera(), mCameraContext.getProfile()); 183 184 if (LOG_RAW_SENSORS) { 185 mRawSensorLogger = new RawSensorLogger(mRecordDir); 186 } 187 } 188 189 /** 190 * Notify recording is completed. This is the successful exit. 191 */ notifyComplete()192 public void notifyComplete() { 193 message("Capture completed!"); 194 195 Uri resultUri = Uri.fromFile(mRecordDir); 196 Intent result = new Intent(); 197 result.setData(resultUri); 198 setResult(Activity.RESULT_OK, result); 199 200 finish(); 201 } 202 203 /** 204 * Notify the user what to do next in text 205 * 206 * @param axis SensorManager.AXIS_X or SensorManager.AXIS_Y or SensorManager.AXIS_Z 207 */ notifyPrompt(int axis)208 private void notifyPrompt(int axis) { 209 // It is not XYZ because of earlier design have different definition of 210 // X and Y 211 final String axisName = "YXZ"; 212 213 message("Manipulate the device in " + axisName.charAt(axis - 1) + 214 " axis (as illustrated) about the pattern."); 215 } 216 217 /** 218 * Ask indicator view to redraw 219 */ redrawIndicator()220 private void redrawIndicator() { 221 mIndicatorView.invalidate(); 222 } 223 224 /** 225 * Switch to a different axis for display and logging 226 * @param axis 227 */ switchAxis(int axis)228 private void switchAxis(int axis) { 229 ImageView imageView = (ImageView) findViewById(R.id.cam_overlay); 230 231 final int [] prompts = {R.drawable.prompt_x, R.drawable.prompt_y, R.drawable.prompt_z}; 232 233 if (axis >=SensorManager.AXIS_X && axis <=SensorManager.AXIS_Z) { 234 imageView.setImageResource(prompts[axis-1]); 235 if (mDeviceRotation != Surface.ROTATION_0 && mDeviceRotation != Surface.ROTATION_180) { 236 imageView.setRotation(90); 237 } 238 mIndicatorView.enableAxis(axis); 239 mRVSensorLogger.updateRegister(mCoverManager.getAxis(axis), axis); 240 notifyPrompt(axis); 241 } else { 242 imageView.setImageDrawable(null); 243 mIndicatorView.enableAxis(AXIS_NONE); 244 } 245 redrawIndicator(); 246 } 247 248 /** 249 * Asynchronized way to call switchAxis. Use this if caller is not on UI thread. 250 * @param axis @param axis SensorManager.AXIS_X or SensorManager.AXIS_Y or SensorManager.AXIS_Z 251 */ switchAxisAsync(int axis)252 public void switchAxisAsync(int axis) { 253 // intended to be called from a non-UI thread 254 final int fAxis = axis; 255 runOnUiThread(new Runnable() { 256 public void run() { 257 // UI code goes here 258 switchAxis(fAxis); 259 } 260 }); 261 } 262 263 /** 264 * Initialize sound pool for user notification 265 */ initSoundPool()266 private void initSoundPool() { 267 mSoundPool = new SoundPool(1 /*maxStreams*/, AudioManager.STREAM_MUSIC, 0); 268 mSoundMap = new HashMap<>(); 269 270 // TODO: add different sound into this 271 mSoundMap.put("start", mSoundPool.load(this, R.raw.start_axis, 1)); 272 mSoundMap.put("end", mSoundPool.load(this, R.raw.next_axis, 1)); 273 mSoundMap.put("half-way", mSoundPool.load(this, R.raw.half_way, 1)); 274 } endSoundPool()275 private void endSoundPool() { 276 mSoundPool.release(); 277 } 278 279 /** 280 * Play notify sound to user 281 * @param name name of the sound to be played 282 */ playNotifySound(String name)283 public void playNotifySound(String name) { 284 Integer id = mSoundMap.get(name); 285 if (id != null) { 286 mSoundPool.play(id.intValue(), 0.75f/*left vol*/, 0.75f/*right vol*/, 0 /*priority*/, 287 0/*loop play*/, 1/*rate*/); 288 } 289 } 290 291 /** 292 * Start the sensor recording 293 */ startRecordSensor()294 public void startRecordSensor() { 295 runOnUiThread(new Runnable() { 296 public void run() { 297 mRVSensorLogger.init(); 298 if (LOG_RAW_SENSORS) { 299 mRawSensorLogger.init(); 300 } 301 } 302 }); 303 } 304 305 /** 306 * Stop the sensor recording 307 */ stopRecordSensor()308 public void stopRecordSensor() { 309 runOnUiThread(new Runnable() { 310 public void run() { 311 mRVSensorLogger.end(); 312 if (LOG_RAW_SENSORS) { 313 mRawSensorLogger.end(); 314 } 315 } 316 }); 317 } 318 319 /** 320 * Start video recording 321 */ startRecordVideo()322 public void startRecordVideo() { 323 mVideoRecorder.init(); 324 } 325 326 /** 327 * Stop video recording 328 */ stopRecordVideo()329 public void stopRecordVideo() { 330 mVideoRecorder.end(); 331 } 332 333 /** 334 * Wait until a sensor recording for a certain axis is fully covered 335 * @param axis 336 */ waitUntilCovered(int axis)337 public void waitUntilCovered(int axis) { 338 mCoverManager.waitUntilCovered(axis); 339 } 340 341 /** 342 * Wait until a sensor recording for a certain axis is halfway covered 343 * @param axis 344 */ waitUntilHalfCovered(int axis)345 public void waitUntilHalfCovered(int axis) { 346 mCoverManager.waitUntilHalfCovered(axis); 347 } 348 349 /** 350 * 351 */ initStoragePath()352 private void initStoragePath() { 353 File rxcvRecDataDir = new File(getExternalFilesDir(null),"RVCVRecData"); 354 355 // Create the storage directory if it does not exist 356 if (! rxcvRecDataDir.exists()) { 357 if (! rxcvRecDataDir.mkdirs()) { 358 Log.e(TAG, "failed to create main data directory"); 359 } 360 } 361 362 mRecordDir = new File(rxcvRecDataDir, new SimpleDateFormat("yyMMdd-hhmmss").format(new Date())); 363 364 if (! mRecordDir.mkdirs()) { 365 Log.e(TAG, "failed to create rec data directory"); 366 } 367 } 368 369 /** 370 * Get the sensor log file path 371 * @return Path of the sensor log file 372 */ getSensorLogFilePath()373 public String getSensorLogFilePath() { 374 return new File(mRecordDir, "sensor.log").getPath(); 375 } 376 377 /** 378 * Get the video recording file path 379 * @return Path of the video recording file 380 */ getVideoRecFilePath()381 public String getVideoRecFilePath() { 382 return new File(mRecordDir, "video.mp4").getPath(); 383 } 384 385 /** 386 * Write out important camera/video information to a JSON file 387 * @param width width of frame 388 * @param height height of frame 389 * @param frameRate frame rate in fps 390 * @param fovW field of view in width direction 391 * @param fovH field of view in height direction 392 */ writeVideoMetaInfo(int width, int height, float frameRate, float fovW, float fovH)393 public void writeVideoMetaInfo(int width, int height, float frameRate, float fovW, float fovH) { 394 try { 395 JsonWriter writer = 396 new JsonWriter( 397 new OutputStreamWriter( 398 new FileOutputStream( 399 new File(mRecordDir, "videometa.json").getPath() 400 ) 401 ) 402 ); 403 writer.beginObject(); 404 writer.name("fovW").value(fovW); 405 writer.name("fovH").value(fovH); 406 writer.name("width").value(width); 407 writer.name("height").value(height); 408 writer.name("frameRate").value(frameRate); 409 writer.endObject(); 410 411 writer.close(); 412 }catch (FileNotFoundException e) { 413 // Not very likely to happen 414 e.printStackTrace(); 415 }catch (IOException e) { 416 // do nothing 417 e.printStackTrace(); 418 Log.e(TAG, "Writing video meta data failed."); 419 } 420 } 421 422 public interface RecordProcedureControllerCallback { startRecordProcedureController()423 public void startRecordProcedureController(); stopRecordProcedureController()424 public void stopRecordProcedureController(); 425 } 426 427 /** 428 * Camera preview control class 429 */ 430 class CameraContext { 431 private Camera mCamera; 432 private CamcorderProfile mProfile; 433 private Camera.CameraInfo mCameraInfo; 434 private RVCVCameraPreview mCameraPreview; 435 436 private int [] mPreferredProfiles = { 437 CamcorderProfile.QUALITY_480P, // smaller -> faster 438 CamcorderProfile.QUALITY_720P, 439 CamcorderProfile.QUALITY_1080P, 440 CamcorderProfile.QUALITY_HIGH // existence guaranteed 441 }; 442 443 private String [] mPreferredFocusMode = { 444 Camera.Parameters.FOCUS_MODE_FIXED, 445 Camera.Parameters.FOCUS_MODE_INFINITY, 446 // the following two modes are more likely to mess up recording 447 // but they are still better than FOCUS_MODE_AUTO, which requires 448 // calling autoFocus explicitly to focus. 449 Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, 450 Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE 451 }; 452 CameraContext()453 CameraContext() { 454 try { 455 // Get the first back-facing camera or set them as null 456 mCamera = null; 457 mCameraInfo = null; 458 mProfile = null; 459 460 int numberOfCameras = Camera.getNumberOfCameras(); 461 Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); 462 for (int i = 0; i < numberOfCameras; ++i) { 463 Camera.getCameraInfo(i, cameraInfo); 464 if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) { 465 mCamera = Camera.open(i); 466 mCameraInfo = cameraInfo; 467 break; 468 } 469 } 470 471 if (mCamera == null) { 472 Log.e(TAG, "Cannot obtain Camera!"); 473 } else { 474 setupCamera(); 475 } 476 } 477 catch (Exception e){ 478 // Camera is not available (in use or does not exist) 479 Log.e(TAG, "Cannot obtain Camera!"); 480 } 481 } 482 483 /** 484 * Find a preferred camera profile and set preview and picture size property accordingly. 485 */ setupCamera()486 void setupCamera() { 487 CamcorderProfile profile = null; 488 boolean isSetNeeded = false; 489 Camera.Parameters param = mCamera.getParameters(); 490 List<Camera.Size> pre_sz = param.getSupportedPreviewSizes(); 491 List<Camera.Size> pic_sz = param.getSupportedPictureSizes(); 492 493 for (int i : mPreferredProfiles) { 494 if (CamcorderProfile.hasProfile(i)) { 495 profile = CamcorderProfile.get(i); 496 497 int valid = 0; 498 for (Camera.Size j : pre_sz) { 499 if (j.width == profile.videoFrameWidth && 500 j.height == profile.videoFrameHeight) { 501 ++valid; 502 break; 503 } 504 } 505 for (Camera.Size j : pic_sz) { 506 if (j.width == profile.videoFrameWidth && 507 j.height == profile.videoFrameHeight) { 508 ++valid; 509 break; 510 } 511 } 512 if (valid == 2) { 513 param.setPreviewSize(profile.videoFrameWidth, profile.videoFrameHeight); 514 param.setPictureSize(profile.videoFrameWidth, profile.videoFrameHeight); 515 isSetNeeded = true; 516 break; 517 } else { 518 profile = null; 519 } 520 } 521 } 522 523 for (String i : mPreferredFocusMode) { 524 if (param.getSupportedFocusModes().contains(i)){ 525 param.setFocusMode(i); 526 isSetNeeded = true; 527 break; 528 } 529 } 530 531 if (isSetNeeded) { 532 mCamera.setParameters(param); 533 } 534 535 if (profile != null) { 536 param = mCamera.getParameters(); //acquire proper fov after change the picture size 537 float fovW = param.getHorizontalViewAngle(); 538 float fovH = param.getVerticalViewAngle(); 539 writeVideoMetaInfo(profile.videoFrameWidth, profile.videoFrameHeight, 540 profile.videoFrameRate, fovW, fovH); 541 } else { 542 Log.e(TAG, "Cannot find a proper video profile"); 543 } 544 mProfile = profile; 545 546 } 547 548 549 /** 550 * Get sensor information of the camera being used 551 */ getCameraInfo()552 public Camera.CameraInfo getCameraInfo() { 553 return mCameraInfo; 554 } 555 556 /** 557 * Get the camera to be previewed 558 * @return Reference to Camera used 559 */ getCamera()560 public Camera getCamera() { 561 return mCamera; 562 } 563 564 /** 565 * Get the camera profile to be used 566 * @return Reference to Camera profile 567 */ getProfile()568 public CamcorderProfile getProfile() { 569 return mProfile; 570 } 571 572 /** 573 * Setup the camera 574 */ init(RVCVRecordActivity.RecordProcedureControllerCallback callback)575 public void init(RVCVRecordActivity.RecordProcedureControllerCallback callback) { 576 if (mCamera != null) { 577 double alpha = mCamera.getParameters().getHorizontalViewAngle()*Math.PI/180.0; 578 int width = mProfile.videoFrameWidth; 579 double fx = width/2/Math.tan(alpha/2.0); 580 581 if (LOCAL_LOGV) Log.v(TAG, "View angle=" 582 + mCamera.getParameters().getHorizontalViewAngle() +" Estimated fx = "+fx); 583 584 mCameraPreview = 585 (RVCVCameraPreview) findViewById(R.id.cam_preview); 586 mCameraPreview.setRecordProcedureControllerCallback(callback); 587 mCameraPreview.init(mCamera, 588 (float)mProfile.videoFrameWidth/mProfile.videoFrameHeight, 589 mCameraInfo.orientation); 590 } else { 591 message("Cannot open camera!"); 592 finish(); 593 } 594 } 595 596 /** 597 * End the camera preview 598 */ end()599 public void end() { 600 if (mCamera != null) { 601 mCamera.release(); // release the camera for other applications 602 mCamera = null; 603 } 604 } 605 } 606 607 /** 608 * Manage a set of RangeCoveredRegister objects 609 */ 610 class CoverageManager { 611 // settings 612 private final int MAX_TILT_ANGLE = 50; // +/- 50 613 //private final int REQUIRED_TILT_ANGLE = 50; // +/- 50 614 private final int TILT_ANGLE_STEP = 5; // 5 degree(s) per step 615 private final int YAW_ANGLE_STEP = 10; // 10 degree(s) per step 616 617 RangeCoveredRegister[] mAxisCovered; 618 CoverageManager()619 CoverageManager() { 620 mAxisCovered = new RangeCoveredRegister[3]; 621 // X AXIS 622 mAxisCovered[0] = new RangeCoveredRegister( 623 -MAX_TILT_ANGLE, +MAX_TILT_ANGLE, TILT_ANGLE_STEP); 624 // Y AXIS 625 mAxisCovered[1] = new RangeCoveredRegister( 626 -MAX_TILT_ANGLE, +MAX_TILT_ANGLE, TILT_ANGLE_STEP); 627 // Z AXIS 628 mAxisCovered[2] = new RangeCoveredRegister(YAW_ANGLE_STEP); 629 } 630 getAxis(int axis)631 public RangeCoveredRegister getAxis(int axis) { 632 // SensorManager.AXIS_X = 1, need offset -1 for mAxisCovered array 633 return mAxisCovered[axis-1]; 634 } 635 waitUntilHalfCovered(int axis)636 public void waitUntilHalfCovered(int axis) { 637 if (axis == SensorManager.AXIS_Z) { 638 waitUntilCovered(axis); 639 } 640 641 // SensorManager.AXIS_X = 1, need offset -1 for mAxisCovered array 642 while(!(mAxisCovered[axis-1].isRangeCovered(-MAX_TILT_ANGLE, -MAX_TILT_ANGLE/2) || 643 mAxisCovered[axis-1].isRangeCovered(MAX_TILT_ANGLE/2, MAX_TILT_ANGLE) ) ) { 644 try { 645 Thread.sleep(500); 646 } catch (InterruptedException e) { 647 if (LOCAL_LOGV) { 648 Log.v(TAG, "waitUntilHalfCovered axis = "+ axis + " is interrupted"); 649 } 650 Thread.currentThread().interrupt(); 651 } 652 } 653 } 654 waitUntilCovered(int axis)655 public void waitUntilCovered(int axis) { 656 // SensorManager.AXIS_X = 1, need offset -1 for mAxisCovered array 657 while(!mAxisCovered[axis-1].isFullyCovered()) { 658 try { 659 Thread.sleep(500); 660 } catch (InterruptedException e) { 661 if (LOCAL_LOGV) { 662 Log.v(TAG, "waitUntilCovered axis = "+ axis + " is interrupted"); 663 } 664 Thread.currentThread().interrupt(); 665 } 666 } 667 } 668 } 669 //////////////////////////////////////////////////////////////////////////////////////////////// 670 671 /** 672 * A class controls the video recording 673 */ 674 class VideoRecorder 675 { 676 private MediaRecorder mRecorder; 677 private CamcorderProfile mProfile; 678 private Camera mCamera; 679 private boolean mRunning = false; 680 VideoRecorder(Camera camera, CamcorderProfile profile)681 VideoRecorder(Camera camera, CamcorderProfile profile){ 682 mCamera = camera; 683 mProfile = profile; 684 } 685 686 /** 687 * Initialize and start recording 688 */ init()689 public void init() { 690 if (mCamera == null || mProfile ==null){ 691 return; 692 } 693 694 mRecorder = new MediaRecorder(); 695 try { 696 mCamera.unlock(); 697 } catch (RuntimeException e) { 698 e.printStackTrace(); 699 try { 700 mRecorder.reset(); 701 mRecorder.release(); 702 } catch (RuntimeException ex) { 703 e.printStackTrace(); 704 } 705 return; 706 } 707 708 try { 709 mRecorder.setCamera(mCamera); 710 mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 711 mRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); 712 mRecorder.setProfile(mProfile); 713 } catch (RuntimeException e) { 714 e.printStackTrace(); 715 return; 716 } 717 718 try { 719 mRecorder.setOutputFile(getVideoRecFilePath()); 720 mRecorder.prepare(); 721 } catch (IOException e) { 722 Log.e(TAG, "Preparation for recording failed."); 723 return; 724 } 725 726 try { 727 mRecorder.start(); 728 } catch (RuntimeException e) { 729 Log.e(TAG, "Starting recording failed."); 730 try { 731 mRecorder.reset(); 732 mRecorder.release(); 733 mCamera.lock(); 734 } catch (RuntimeException ex1) { 735 e.printStackTrace(); 736 } 737 return; 738 } 739 mRunning = true; 740 } 741 742 /** 743 * Stop recording 744 */ end()745 public void end() { 746 if (mRunning) { 747 try { 748 mRecorder.stop(); 749 mRecorder.reset(); 750 mRecorder.release(); 751 mCamera.lock(); 752 } catch (RuntimeException e) { 753 e.printStackTrace(); 754 Log.e(TAG, "Runtime error in stopping recording."); 755 } 756 mRunning = false; 757 } 758 mRecorder = null; 759 } 760 761 } 762 763 //////////////////////////////////////////////////////////////////////////////////////////////// 764 765 /** 766 * Log all raw sensor readings, for Rotation Vector sensor algorithms research 767 */ 768 class RawSensorLogger implements SensorEventListener { 769 private final String TAG = "RawSensorLogger"; 770 771 private final static int SENSOR_RATE = SensorManager.SENSOR_DELAY_FASTEST; 772 private File mRecPath; 773 774 SensorManager mSensorManager; 775 Sensor mAccSensor, mGyroSensor, mMagSensor; 776 OutputStreamWriter mAccLogWriter, mGyroLogWriter, mMagLogWriter; 777 778 private float[] mRTemp = new float[16]; 779 RawSensorLogger(File recPath)780 RawSensorLogger(File recPath) { 781 mRecPath = recPath; 782 } 783 784 /** 785 * Initialize and start recording 786 */ init()787 public void init() { 788 mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE); 789 790 mAccSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 791 mGyroSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED); 792 mMagSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED); 793 794 mSensorManager.registerListener(this, mAccSensor, SENSOR_RATE); 795 mSensorManager.registerListener(this, mGyroSensor, SENSOR_RATE); 796 mSensorManager.registerListener(this, mMagSensor, SENSOR_RATE); 797 798 try { 799 mAccLogWriter= new OutputStreamWriter( 800 new FileOutputStream(new File(mRecPath, "raw_acc.log"))); 801 mGyroLogWriter= new OutputStreamWriter( 802 new FileOutputStream(new File(mRecPath, "raw_uncal_gyro.log"))); 803 mMagLogWriter= new OutputStreamWriter( 804 new FileOutputStream(new File(mRecPath, "raw_uncal_mag.log"))); 805 806 } catch (FileNotFoundException e) { 807 Log.e(TAG, "Sensor log file open failed: " + e.toString()); 808 } 809 } 810 811 /** 812 * Stop recording and clean up 813 */ end()814 public void end() { 815 mSensorManager.flush(this); 816 mSensorManager.unregisterListener(this); 817 818 try { 819 if (mAccLogWriter != null) { 820 OutputStreamWriter writer = mAccLogWriter; 821 mAccLogWriter = null; 822 writer.close(); 823 } 824 if (mGyroLogWriter != null) { 825 OutputStreamWriter writer = mGyroLogWriter; 826 mGyroLogWriter = null; 827 writer.close(); 828 } 829 if (mMagLogWriter != null) { 830 OutputStreamWriter writer = mMagLogWriter; 831 mMagLogWriter = null; 832 writer.close(); 833 } 834 835 } catch (IOException e) { 836 Log.e(TAG, "Sensor log file close failed: " + e.toString()); 837 } 838 } 839 840 @Override onAccuracyChanged(Sensor sensor, int i)841 public void onAccuracyChanged(Sensor sensor, int i) { 842 // do not care 843 } 844 845 @Override onSensorChanged(SensorEvent event)846 public void onSensorChanged(SensorEvent event) { 847 OutputStreamWriter writer=null; 848 switch(event.sensor.getType()) { 849 case Sensor.TYPE_ACCELEROMETER: 850 writer = mAccLogWriter; 851 break; 852 case Sensor.TYPE_GYROSCOPE_UNCALIBRATED: 853 writer = mGyroLogWriter; 854 break; 855 case Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED: 856 writer = mMagLogWriter; 857 break; 858 859 } 860 if (writer!=null) { 861 float[] data = event.values; 862 try { 863 if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { 864 writer.write(String.format("%d %f %f %f\r\n", 865 event.timestamp, data[0], data[1], data[2])); 866 }else // TYPE_GYROSCOPE_UNCALIBRATED and TYPE_MAGNETIC_FIELD_UNCALIBRATED 867 { 868 writer.write(String.format("%d %f %f %f %f %f %f\r\n", event.timestamp, 869 data[0], data[1], data[2], data[3], data[4], data[5])); 870 } 871 }catch (IOException e) 872 { 873 Log.e(TAG, "Write to raw sensor log file failed."); 874 } 875 876 } 877 } 878 } 879 880 /** 881 * Rotation sensor logger class 882 */ 883 class RVSensorLogger implements SensorEventListener { 884 private final String TAG = "RVSensorLogger"; 885 886 private final static int SENSOR_RATE = SensorManager.SENSOR_DELAY_FASTEST; 887 RangeCoveredRegister mRegister; 888 int mAxis; 889 RVCVRecordActivity mActivity; 890 891 SensorManager mSensorManager; 892 Sensor mRVSensor; 893 OutputStreamWriter mLogWriter; 894 895 private float[] mRTemp = new float[16]; 896 RVSensorLogger(RVCVRecordActivity activity)897 RVSensorLogger(RVCVRecordActivity activity) { 898 mActivity = activity; 899 } 900 901 /** 902 * Initialize and start recording 903 */ init()904 public void init() { 905 mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE); 906 if (mSensorManager == null) { 907 Log.e(TAG,"SensorManager is null!"); 908 } 909 mRVSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); 910 if (mRVSensor != null) { 911 if (LOCAL_LOGV) Log.v(TAG, "Got RV Sensor"); 912 }else { 913 Log.e(TAG, "Did not get RV sensor"); 914 } 915 if(mSensorManager.registerListener(this, mRVSensor, SENSOR_RATE)) { 916 if (LOCAL_LOGV) Log.v(TAG,"Register listener successfull"); 917 } else { 918 Log.e(TAG,"Register listener failed"); 919 } 920 921 try { 922 mLogWriter= new OutputStreamWriter( 923 new FileOutputStream(mActivity.getSensorLogFilePath())); 924 } catch (FileNotFoundException e) { 925 Log.e(TAG, "Sensor log file open failed: " + e.toString()); 926 } 927 } 928 929 /** 930 * Stop recording and clean up 931 */ end()932 public void end() { 933 mSensorManager.flush(this); 934 mSensorManager.unregisterListener(this); 935 936 try { 937 if (mLogWriter != null) { 938 OutputStreamWriter writer = mLogWriter; 939 mLogWriter = null; 940 writer.close(); 941 } 942 } catch (IOException e) { 943 Log.e(TAG, "Sensor log file close failed: " + e.toString()); 944 } 945 946 updateRegister(null, AXIS_NONE); 947 } 948 onNewData(float[] data, long timestamp)949 private void onNewData(float[] data, long timestamp) { 950 // LOG 951 try { 952 if (mLogWriter != null) { 953 mLogWriter.write(String.format("%d %f %f %f %f\r\n", timestamp, 954 data[3], data[0], data[1], data[2])); 955 } 956 } catch (IOException e) { 957 Log.e(TAG, "Sensor log file write failed: " + e.toString()); 958 } 959 960 // Update UI 961 if (mRegister != null) { 962 int d = 0; 963 int dx, dy, dz; 964 boolean valid = false; 965 SensorManager.getRotationMatrixFromVector(mRTemp, data); 966 967 dx = (int)(Math.asin(mRTemp[8])*(180.0/Math.PI)); 968 dy = (int)(Math.asin(mRTemp[9])*(180.0/Math.PI)); 969 dz = (int)((Math.atan2(mRTemp[4], mRTemp[0])+Math.PI)*(180.0/Math.PI)); 970 971 switch(mAxis) { 972 case SensorManager.AXIS_X: 973 d = dx; 974 valid = (Math.abs(dy) < 30); 975 break; 976 case SensorManager.AXIS_Y: 977 d = dy; 978 valid = (Math.abs(dx) < 30); 979 break; 980 case SensorManager.AXIS_Z: 981 d = dz; 982 valid = (Math.abs(dx) < 20 && Math.abs(dy) < 20); 983 break; 984 } 985 986 if (valid) { 987 mRegister.update(d); 988 mActivity.redrawIndicator(); 989 } 990 } 991 992 } 993 updateRegister(RangeCoveredRegister reg, int axis)994 public void updateRegister(RangeCoveredRegister reg, int axis) { 995 mRegister = reg; 996 mAxis = axis; 997 } 998 999 1000 @Override onAccuracyChanged(Sensor sensor, int i)1001 public void onAccuracyChanged(Sensor sensor, int i) { 1002 // do not care 1003 } 1004 1005 @Override onSensorChanged(SensorEvent event)1006 public void onSensorChanged(SensorEvent event) { 1007 if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) { 1008 onNewData(event.values, event.timestamp); 1009 } 1010 } 1011 } 1012 1013 1014 //////////////////////////////////////////////////////////////////////////////////////////////// 1015 1016 /** 1017 * Controls the over all logic of record procedure: first x-direction, then y-direction and 1018 * then z-direction. 1019 */ 1020 class RecordProcedureController implements Runnable { 1021 private static final boolean LOCAL_LOGV = false; 1022 1023 private final RVCVRecordActivity mActivity; 1024 private Thread mThread = null; 1025 RecordProcedureController(RVCVRecordActivity activity)1026 RecordProcedureController(RVCVRecordActivity activity) { 1027 mActivity = activity; 1028 mThread = new Thread(this); 1029 mThread.start(); 1030 } 1031 1032 /** 1033 * Run the record procedure 1034 */ run()1035 public void run() { 1036 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread Started."); 1037 //start recording & logging 1038 delay(2000); 1039 1040 init(); 1041 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread init() finished."); 1042 1043 // test 3 axis 1044 // It is in YXZ order because UI element design use opposite definition 1045 // of XY axis. To ensure the user see X Y Z, it is flipped here. 1046 recordAxis(SensorManager.AXIS_Y); 1047 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread axis 0 finished."); 1048 1049 recordAxis(SensorManager.AXIS_X); 1050 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread axis 1 finished."); 1051 1052 recordAxis(SensorManager.AXIS_Z); 1053 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread axis 2 finished."); 1054 1055 delay(1000); 1056 end(); 1057 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread End."); 1058 } 1059 delay(int milli)1060 private void delay(int milli) { 1061 try{ 1062 Thread.sleep(milli); 1063 } catch(InterruptedException e) { 1064 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread Interrupted."); 1065 } 1066 } init()1067 private void init() { 1068 // start video recording 1069 mActivity.startRecordVideo(); 1070 1071 // start sensor logging & listening 1072 mActivity.startRecordSensor(); 1073 } 1074 end()1075 private void end() { 1076 // stop video recording 1077 mActivity.stopRecordVideo(); 1078 1079 // stop sensor logging 1080 mActivity.stopRecordSensor(); 1081 1082 // notify ui complete 1083 runOnUiThread(new Runnable(){ 1084 public void run() { 1085 mActivity.notifyComplete(); 1086 } 1087 }); 1088 } 1089 recordAxis(int axis)1090 private void recordAxis(int axis) { 1091 // delay 2 seconds? 1092 delay(1000); 1093 1094 // change ui 1095 mActivity.switchAxisAsync(axis); 1096 1097 // play start sound 1098 mActivity.playNotifySound("start"); 1099 1100 if (axis != SensorManager.AXIS_Z) { 1101 // wait until axis half covered 1102 mActivity.waitUntilHalfCovered(axis); 1103 1104 // play half way sound 1105 mActivity.playNotifySound("half-way"); 1106 } 1107 1108 // wait until axis covered 1109 mActivity.waitUntilCovered(axis); 1110 1111 // play stop sound 1112 mActivity.playNotifySound("end"); 1113 } 1114 1115 /** 1116 * Force quit 1117 */ quit()1118 public void quit() { 1119 mThread.interrupt(); 1120 try { 1121 if (LOCAL_LOGV) Log.v(TAG, "Wait for controller to end"); 1122 1123 // stop video recording 1124 mActivity.stopRecordVideo(); 1125 1126 // stop sensor logging 1127 mActivity.stopRecordSensor(); 1128 1129 } catch (Exception e) 1130 { 1131 e.printStackTrace(); 1132 } 1133 } 1134 } 1135 1136 } 1137