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