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