1 /* 2 * Copyright (C) 2013 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 package com.android.cts.verifier.camera.video; 17 18 import android.app.AlertDialog; 19 import android.content.Context; 20 import android.content.DialogInterface; 21 import android.graphics.Matrix; 22 import android.graphics.SurfaceTexture; 23 import android.hardware.Camera; 24 import android.hardware.Camera.CameraInfo; 25 import android.hardware.Camera.Size; 26 import android.hardware.camera2.CameraAccessException; 27 import android.hardware.camera2.CameraCharacteristics; 28 import android.hardware.camera2.CameraManager; 29 import android.media.CamcorderProfile; 30 import android.media.MediaPlayer; 31 import android.media.MediaRecorder; 32 import android.os.Bundle; 33 import android.os.Environment; 34 import android.os.Handler; 35 import android.util.Log; 36 import android.view.Surface; 37 import android.view.TextureView; 38 import android.view.View; 39 import android.widget.AdapterView; 40 import android.widget.ArrayAdapter; 41 import android.widget.Button; 42 import android.widget.ImageButton; 43 import android.widget.Spinner; 44 import android.widget.TextView; 45 import android.widget.Toast; 46 import android.widget.VideoView; 47 48 import com.android.cts.verifier.PassFailButtons; 49 import com.android.cts.verifier.R; 50 51 import java.io.File; 52 import java.io.IOException; 53 import java.text.SimpleDateFormat; 54 import java.util.ArrayList; 55 import java.util.Comparator; 56 import java.util.Date; 57 import java.util.List; 58 import java.util.TreeSet; 59 60 61 /** 62 * Tests for manual verification of camera video capture 63 */ 64 public class CameraVideoActivity extends PassFailButtons.Activity 65 implements TextureView.SurfaceTextureListener { 66 67 private static final String TAG = "CtsCameraVideo"; 68 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 69 private static final int MEDIA_TYPE_IMAGE = 1; 70 private static final int MEDIA_TYPE_VIDEO = 2; 71 private static final int VIDEO_LENGTH = 3000; // in ms 72 73 private TextureView mPreviewView; 74 private SurfaceTexture mPreviewTexture; 75 private int mPreviewTexWidth; 76 private int mPreviewTexHeight; 77 private int mPreviewRotation; 78 private int mVideoRotation; 79 80 private VideoView mPlaybackView; 81 82 private Spinner mCameraSpinner; 83 private Spinner mResolutionSpinner; 84 85 private int mCurrentCameraId = -1; 86 private Camera mCamera; 87 private boolean mIsExternalCamera; 88 89 private MediaRecorder mMediaRecorder; 90 91 private List<Size> mPreviewSizes; 92 private Size mNextPreviewSize; 93 private Size mPreviewSize; 94 private List<Integer> mVideoSizeIds; 95 private int mCurrentVideoSizeId; 96 97 private boolean isRecording = false; 98 private boolean isPlayingBack = false; 99 private Button captureButton; 100 private ImageButton mPassButton; 101 private ImageButton mFailButton; 102 103 private TextView mStatusLabel; 104 105 private TreeSet<String> mTestedCombinations = new TreeSet<String>(); 106 private TreeSet<String> mUntestedCombinations = new TreeSet<String>(); 107 108 private File outputVideoFile; 109 110 /** 111 * @see #MEDIA_TYPE_IMAGE 112 * @see #MEDIA_TYPE_VIDEO 113 */ getOutputMediaFile(int type)114 private static File getOutputMediaFile(int type) { 115 // Question: why do I need to comment this to get it working? 116 // Logcat says "external storage not ready" 117 // if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) { 118 // Log.e(TAG, "external storage not ready"); 119 // return null; 120 // } 121 122 File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory( 123 Environment.DIRECTORY_MOVIES), TAG); 124 125 if (!mediaStorageDir.exists()) { 126 if (!mediaStorageDir.mkdirs()) { 127 Log.d(TAG, "failed to create directory"); 128 return null; 129 } 130 } 131 132 String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); 133 File mediaFile; 134 if (type == MEDIA_TYPE_IMAGE) { 135 mediaFile = new File(mediaStorageDir.getPath() + File.separator + 136 "IMG_" + timeStamp + ".jpg"); 137 } else if (type == MEDIA_TYPE_VIDEO) { 138 mediaFile = new File(mediaStorageDir.getPath() + File.separator + 139 "VID_" + timeStamp + ".mp4"); 140 if (VERBOSE) { 141 Log.v(TAG, "getOutputMediaFile: output file " + mediaFile.getPath()); 142 } 143 } else { 144 return null; 145 } 146 147 return mediaFile; 148 } 149 150 private static final int BIT_RATE_720P = 8000000; 151 private static final int BIT_RATE_MIN = 64000; 152 private static final int BIT_RATE_MAX = BIT_RATE_720P; 153 getVideoBitRate(Camera.Size sz)154 private int getVideoBitRate(Camera.Size sz) { 155 int rate = BIT_RATE_720P; 156 float scaleFactor = sz.height * sz.width / (float)(1280 * 720); 157 rate = (int)(rate * scaleFactor); 158 159 // Clamp to the MIN, MAX range. 160 return Math.max(BIT_RATE_MIN, Math.min(BIT_RATE_MAX, rate)); 161 } 162 prepareVideoRecorder()163 private boolean prepareVideoRecorder() { 164 165 mMediaRecorder = new MediaRecorder(); 166 167 // Step 1: unlock and set camera to MediaRecorder 168 mCamera.unlock(); 169 mMediaRecorder.setCamera(mCamera); 170 171 // Step 2: set sources 172 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); 173 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 174 175 // Step 3: set a CamcorderProfile 176 if (mIsExternalCamera) { 177 Camera.Size recordSize = null; 178 switch (mCurrentVideoSizeId) { 179 case CamcorderProfile.QUALITY_QCIF: 180 recordSize = mCamera.new Size(176, 144); 181 break; 182 case CamcorderProfile.QUALITY_QVGA: 183 recordSize = mCamera.new Size(320, 240); 184 break; 185 case CamcorderProfile.QUALITY_CIF: 186 recordSize = mCamera.new Size(352, 288); 187 break; 188 case CamcorderProfile.QUALITY_480P: 189 recordSize = mCamera.new Size(720, 480); 190 break; 191 case CamcorderProfile.QUALITY_720P: 192 recordSize = mCamera.new Size(1280, 720); 193 break; 194 default: 195 String msg = "Unknown CamcorderProfile: " + mCurrentVideoSizeId; 196 Log.e(TAG, msg); 197 releaseMediaRecorder(); 198 throw new AssertionError(msg); 199 } 200 201 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 202 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); 203 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); 204 mMediaRecorder.setVideoEncodingBitRate(getVideoBitRate(recordSize)); 205 mMediaRecorder.setVideoSize(recordSize.width, recordSize.height); 206 } else { 207 mMediaRecorder.setProfile(CamcorderProfile.get(mCurrentCameraId, mCurrentVideoSizeId)); 208 } 209 210 // Step 4: set output file 211 outputVideoFile = getOutputMediaFile(MEDIA_TYPE_VIDEO); 212 mMediaRecorder.setOutputFile(outputVideoFile.toString()); 213 214 // Step 5: set preview output 215 // This is not necessary since preview has been taken care of 216 217 // Step 6: set orientation hint 218 mMediaRecorder.setOrientationHint(mVideoRotation); 219 220 // Step 7: prepare configured MediaRecorder 221 try { 222 mMediaRecorder.prepare(); 223 } catch (IOException e) { 224 Log.e(TAG, "IOException preparing MediaRecorder: ", e); 225 releaseMediaRecorder(); 226 throw new AssertionError(e); 227 } 228 229 mMediaRecorder.setOnErrorListener( 230 new MediaRecorder.OnErrorListener() { 231 @Override 232 public void onError(MediaRecorder mr, int what, int extra) { 233 if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) { 234 Log.e(TAG, "unknown error in media recorder, error: " + extra); 235 } else { 236 Log.e(TAG, "media recorder server died, error: " + extra); 237 } 238 239 failTest("Media recorder error."); 240 } 241 }); 242 243 if (VERBOSE) { 244 Log.v(TAG, "prepareVideoRecorder: prepared configured MediaRecorder"); 245 } 246 247 return true; 248 } 249 250 @Override onCreate(Bundle savedInstanceState)251 public void onCreate(Bundle savedInstanceState) { 252 super.onCreate(savedInstanceState); 253 254 setContentView(R.layout.camera_video); 255 setPassFailButtonClickListeners(); 256 setInfoResources(R.string.camera_video, R.string.video_info, /*viewId*/-1); 257 258 mPreviewView = (TextureView) findViewById(R.id.video_capture); 259 mPlaybackView = (VideoView) findViewById(R.id.video_playback); 260 mPlaybackView.setOnCompletionListener(mPlaybackViewListener); 261 262 captureButton = (Button) findViewById(R.id.record_button); 263 mPassButton = (ImageButton) findViewById(R.id.pass_button); 264 mFailButton = (ImageButton) findViewById(R.id.fail_button); 265 mPassButton.setEnabled(false); 266 mFailButton.setEnabled(true); 267 268 mPreviewView.setSurfaceTextureListener(this); 269 270 int numCameras = Camera.getNumberOfCameras(); 271 String[] cameraNames = new String[numCameras]; 272 for (int i = 0; i < numCameras; i++) { 273 cameraNames[i] = "Camera " + i; 274 mUntestedCombinations.add("All combinations for Camera " + i + "\n"); 275 } 276 if (VERBOSE) { 277 Log.v(TAG, "onCreate: number of cameras=" + numCameras); 278 } 279 mCameraSpinner = (Spinner) findViewById(R.id.cameras_selection); 280 mCameraSpinner.setAdapter( 281 new ArrayAdapter<String>( 282 this, R.layout.cf_format_list_item, cameraNames)); 283 mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener); 284 285 mResolutionSpinner = (Spinner) findViewById(R.id.resolution_selection); 286 mResolutionSpinner.setOnItemSelectedListener(mResolutionSelectedListener); 287 288 mStatusLabel = (TextView) findViewById(R.id.status_label); 289 } 290 291 @Override onResume()292 public void onResume() { 293 super.onResume(); 294 295 setUpCamera(mCameraSpinner.getSelectedItemPosition()); 296 if (VERBOSE) { 297 Log.v(TAG, "onResume: camera has been setup"); 298 } 299 300 setUpCaptureButton(); 301 if (VERBOSE) { 302 Log.v(TAG, "onResume: captureButton has been setup"); 303 } 304 305 } 306 307 @Override onPause()308 public void onPause() { 309 super.onPause(); 310 311 releaseMediaRecorder(); 312 shutdownCamera(); 313 mPreviewTexture = null; 314 } 315 316 private MediaPlayer.OnCompletionListener mPlaybackViewListener = 317 new MediaPlayer.OnCompletionListener() { 318 319 @Override 320 public void onCompletion(MediaPlayer mp) { 321 isPlayingBack = false; 322 mPlaybackView.stopPlayback(); 323 captureButton.setEnabled(true); 324 mStatusLabel.setText(getResources().getString(R.string.status_ready)); 325 } 326 327 }; 328 releaseMediaRecorder()329 private void releaseMediaRecorder() { 330 if (mMediaRecorder != null) { 331 mMediaRecorder.reset(); 332 mMediaRecorder.release(); 333 mMediaRecorder = null; 334 mCamera.lock(); // check here, lock camera for later use 335 } 336 } 337 338 @Override getTestDetails()339 public String getTestDetails() { 340 StringBuilder reportBuilder = new StringBuilder(); 341 reportBuilder.append("Tested combinations:\n"); 342 for (String combination : mTestedCombinations) { 343 reportBuilder.append(combination); 344 } 345 reportBuilder.append("Untested combinations:\n"); 346 for (String combination : mUntestedCombinations) { 347 reportBuilder.append(combination); 348 } 349 return reportBuilder.toString(); 350 } 351 352 @Override onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)353 public void onSurfaceTextureAvailable(SurfaceTexture surface, 354 int width, int height) { 355 mPreviewTexture = surface; 356 mPreviewTexWidth = width; 357 mPreviewTexHeight = height; 358 if (mCamera != null) { 359 startPreview(); 360 } 361 } 362 363 @Override onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)364 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 365 // Ignored, Camera does all the work for us 366 } 367 368 @Override onSurfaceTextureDestroyed(SurfaceTexture surface)369 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 370 return true; 371 } 372 373 374 @Override onSurfaceTextureUpdated(SurfaceTexture surface)375 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 376 // Invoked every time there's a new Camera preview frame 377 } 378 379 private AdapterView.OnItemSelectedListener mCameraSpinnerListener = 380 new AdapterView.OnItemSelectedListener() { 381 @Override 382 public void onItemSelected(AdapterView<?> parent, 383 View view, int pos, long id) { 384 if (mCurrentCameraId != pos) { 385 setUpCamera(pos); 386 } 387 } 388 389 @Override 390 public void onNothingSelected(AdapterView<?> parent) { 391 // Intentionally left blank 392 } 393 394 }; 395 396 private AdapterView.OnItemSelectedListener mResolutionSelectedListener = 397 new AdapterView.OnItemSelectedListener() { 398 @Override 399 public void onItemSelected(AdapterView<?> parent, 400 View view, int position, long id) { 401 if (mVideoSizeIds.get(position) != mCurrentVideoSizeId) { 402 mCurrentVideoSizeId = mVideoSizeIds.get(position); 403 if (VERBOSE) { 404 Log.v(TAG, "onItemSelected: mCurrentVideoSizeId = " + 405 mCurrentVideoSizeId); 406 } 407 mNextPreviewSize = matchPreviewRecordSize(); 408 if (VERBOSE) { 409 Log.v(TAG, "onItemSelected: setting preview size " 410 + mNextPreviewSize.width + "x" + mNextPreviewSize.height); 411 } 412 413 startPreview(); 414 if (VERBOSE) { 415 Log.v(TAG, "onItemSelected: started new preview"); 416 } 417 } 418 } 419 420 @Override 421 public void onNothingSelected(AdapterView<?> parent) { 422 // Intentionally left blank 423 } 424 425 }; 426 427 setUpCaptureButton()428 private void setUpCaptureButton() { 429 captureButton.setOnClickListener ( 430 new View.OnClickListener() { 431 @Override 432 public void onClick(View V) { 433 if ((!isRecording) && (!isPlayingBack)) { 434 if (prepareVideoRecorder()) { 435 mMediaRecorder.start(); 436 if (VERBOSE) { 437 Log.v(TAG, "onClick: started mMediaRecorder"); 438 } 439 isRecording = true; 440 captureButton.setEnabled(false); 441 mStatusLabel.setText(getResources() 442 .getString(R.string.status_recording)); 443 } else { 444 releaseMediaRecorder(); 445 Log.e(TAG, "media recorder cannot be set up"); 446 failTest("Unable to set up media recorder."); 447 } 448 Handler h = new Handler(); 449 Runnable mDelayedPreview = new Runnable() { 450 @Override 451 public void run() { 452 mMediaRecorder.stop(); 453 releaseMediaRecorder(); 454 455 mPlaybackView.setVideoPath(outputVideoFile.getPath()); 456 mPlaybackView.start(); 457 isRecording = false; 458 isPlayingBack = true; 459 mStatusLabel.setText(getResources() 460 .getString(R.string.status_playback)); 461 String combination = "Camera " + mCurrentCameraId + ", " + 462 mCurrentVideoSizeId + "\n"; 463 mUntestedCombinations.remove(combination); 464 mTestedCombinations.add(combination); 465 466 if (mUntestedCombinations.isEmpty()) { 467 mPassButton.setEnabled(true); 468 if (VERBOSE) { 469 Log.v(TAG, "run: test success"); 470 } 471 } 472 } 473 }; 474 h.postDelayed(mDelayedPreview, VIDEO_LENGTH); 475 } 476 477 } 478 } 479 ); 480 } 481 482 private class VideoSizeNamePair { 483 private int sizeId; 484 private String sizeName; 485 VideoSizeNamePair(int id, String name)486 public VideoSizeNamePair(int id, String name) { 487 sizeId = id; 488 sizeName = name; 489 } 490 getSizeId()491 public int getSizeId() { 492 return sizeId; 493 } 494 getSizeName()495 public String getSizeName() { 496 return sizeName; 497 } 498 } 499 getVideoSizeNamePairs(int cameraId)500 private ArrayList<VideoSizeNamePair> getVideoSizeNamePairs(int cameraId) { 501 int[] qualityArray = { 502 CamcorderProfile.QUALITY_LOW, 503 CamcorderProfile.QUALITY_HIGH, 504 CamcorderProfile.QUALITY_QCIF, // 176x144 505 CamcorderProfile.QUALITY_QVGA, // 320x240 506 CamcorderProfile.QUALITY_CIF, // 352x288 507 CamcorderProfile.QUALITY_480P, // 720x480 508 CamcorderProfile.QUALITY_720P, // 1280x720 509 CamcorderProfile.QUALITY_1080P, // 1920x1080 or 1920x1088 510 CamcorderProfile.QUALITY_2160P 511 }; 512 513 final Camera.Size skip = mCamera.new Size(-1, -1); 514 Camera.Size[] videoSizeArray = { 515 skip, 516 skip, 517 mCamera.new Size(176, 144), 518 mCamera.new Size(320, 240), 519 mCamera.new Size(352, 288), 520 mCamera.new Size(720, 480), 521 mCamera.new Size(1280, 720), 522 skip, 523 skip 524 }; 525 526 String[] nameArray = { 527 "LOW", 528 "HIGH", 529 "QCIF", 530 "QVGA", 531 "CIF", 532 "480P", 533 "720P", 534 "1080P", 535 "2160P" 536 }; 537 538 ArrayList<VideoSizeNamePair> availableSizes = 539 new ArrayList<VideoSizeNamePair> (); 540 541 Camera.Parameters p = mCamera.getParameters(); 542 List<Camera.Size> supportedVideoSizes = p.getSupportedVideoSizes(); 543 for (int i = 0; i < qualityArray.length; i++) { 544 if (mIsExternalCamera) { 545 Camera.Size videoSz = videoSizeArray[i]; 546 if (videoSz.equals(skip)) { 547 continue; 548 } 549 if (supportedVideoSizes.contains(videoSz)) { 550 VideoSizeNamePair pair = new VideoSizeNamePair(qualityArray[i], nameArray[i]); 551 availableSizes.add(pair); 552 } 553 } else { 554 if (CamcorderProfile.hasProfile(cameraId, qualityArray[i])) { 555 VideoSizeNamePair pair = new VideoSizeNamePair(qualityArray[i], nameArray[i]); 556 availableSizes.add(pair); 557 } 558 } 559 } 560 return availableSizes; 561 } 562 563 static class ResolutionQuality { 564 private int videoSizeId; 565 private int width; 566 private int height; 567 ResolutionQuality()568 public ResolutionQuality() { 569 // intentionally left blank 570 } ResolutionQuality(int newSizeId, int newWidth, int newHeight)571 public ResolutionQuality(int newSizeId, int newWidth, int newHeight) { 572 videoSizeId = newSizeId; 573 width = newWidth; 574 height = newHeight; 575 } 576 } 577 findRecordSize(int cameraId)578 private Size findRecordSize(int cameraId) { 579 int[] possibleQuality = { 580 CamcorderProfile.QUALITY_LOW, 581 CamcorderProfile.QUALITY_HIGH, 582 CamcorderProfile.QUALITY_QCIF, 583 CamcorderProfile.QUALITY_QVGA, 584 CamcorderProfile.QUALITY_CIF, 585 CamcorderProfile.QUALITY_480P, 586 CamcorderProfile.QUALITY_720P, 587 CamcorderProfile.QUALITY_1080P, 588 CamcorderProfile.QUALITY_2160P 589 }; 590 591 final Camera.Size skip = mCamera.new Size(-1, -1); 592 Camera.Size[] videoSizeArray = { 593 skip, 594 skip, 595 mCamera.new Size(176, 144), 596 mCamera.new Size(320, 240), 597 mCamera.new Size(352, 288), 598 mCamera.new Size(720, 480), 599 mCamera.new Size(1280, 720), 600 skip, 601 skip 602 }; 603 604 ArrayList<ResolutionQuality> qualityList = new ArrayList<ResolutionQuality>(); 605 Camera.Parameters p = mCamera.getParameters(); 606 List<Camera.Size> supportedVideoSizes = p.getSupportedVideoSizes(); 607 for (int i = 0; i < possibleQuality.length; i++) { 608 if (mIsExternalCamera) { 609 Camera.Size videoSz = videoSizeArray[i]; 610 if (videoSz.equals(skip)) { 611 continue; 612 } 613 if (supportedVideoSizes.contains(videoSz)) { 614 qualityList.add(new ResolutionQuality(possibleQuality[i], 615 videoSz.width, videoSz.height)); 616 } 617 } else { 618 if (CamcorderProfile.hasProfile(cameraId, possibleQuality[i])) { 619 CamcorderProfile profile = CamcorderProfile.get(cameraId, possibleQuality[i]); 620 qualityList.add(new ResolutionQuality(possibleQuality[i], 621 profile.videoFrameWidth, profile.videoFrameHeight)); 622 } 623 } 624 } 625 626 Size recordSize = null; 627 for (int i = 0; i < qualityList.size(); i++) { 628 if (mCurrentVideoSizeId == qualityList.get(i).videoSizeId) { 629 recordSize = mCamera.new Size(qualityList.get(i).width, 630 qualityList.get(i).height); 631 break; 632 } 633 } 634 635 if (recordSize == null) { 636 Log.e(TAG, "findRecordSize: did not find a match"); 637 failTest("Cannot find video size"); 638 } 639 return recordSize; 640 } 641 642 // Match preview size with current recording size mCurrentVideoSizeId matchPreviewRecordSize()643 private Size matchPreviewRecordSize() { 644 Size recordSize = findRecordSize(mCurrentCameraId); 645 646 Size matchedSize = null; 647 // First try to find exact match in size 648 for (int i = 0; i < mPreviewSizes.size(); i++) { 649 if (mPreviewSizes.get(i).equals(recordSize)) { 650 matchedSize = mCamera.new Size(recordSize.width, recordSize.height); 651 break; 652 } 653 } 654 // Second try to find same ratio in size 655 if (matchedSize == null) { 656 for (int i = mPreviewSizes.size() - 1; i >= 0; i--) { 657 if (mPreviewSizes.get(i).width * recordSize.height == 658 mPreviewSizes.get(i).height * recordSize.width) { 659 matchedSize = mCamera.new Size(mPreviewSizes.get(i).width, 660 mPreviewSizes.get(i).height); 661 break; 662 } 663 } 664 } 665 //Third try to find one with similar if not the same apect ratio 666 if (matchedSize == null) { 667 for (int i = mPreviewSizes.size() - 1; i >= 0; i--) { 668 if (Math.abs((float)mPreviewSizes.get(i).width * recordSize.height / 669 mPreviewSizes.get(i).height / recordSize.width - 1) < 0.12) { 670 matchedSize = mCamera.new Size(mPreviewSizes.get(i).width, 671 mPreviewSizes.get(i).height); 672 break; 673 } 674 } 675 } 676 // Last resort, just use the first preview size 677 if (matchedSize == null) { 678 matchedSize = mCamera.new Size(mPreviewSizes.get(0).width, 679 mPreviewSizes.get(0).height); 680 } 681 682 if (VERBOSE) { 683 Log.v(TAG, "matchPreviewRecordSize " + matchedSize.width + "x" + matchedSize.height); 684 } 685 686 return matchedSize; 687 } 688 setUpCamera(int id)689 private void setUpCamera(int id) { 690 shutdownCamera(); 691 692 mCurrentCameraId = id; 693 try { 694 mCamera = Camera.open(id); 695 } 696 catch (Exception e) { 697 Log.e(TAG, "camera is not available", e); 698 failTest("camera not available" + e.getMessage()); 699 return; 700 } 701 mIsExternalCamera = isExternalCamera(id); 702 703 Camera.Parameters p = mCamera.getParameters(); 704 if (VERBOSE) { 705 Log.v(TAG, "setUpCamera: setUpCamera got camera parameters"); 706 } 707 708 // Get preview resolutions 709 List<Size> unsortedSizes = p.getSupportedPreviewSizes(); 710 711 class SizeCompare implements Comparator<Size> { 712 @Override 713 public int compare(Size lhs, Size rhs) { 714 if (lhs.width < rhs.width) return -1; 715 if (lhs.width > rhs.width) return 1; 716 if (lhs.height < rhs.height) return -1; 717 if (lhs.height > rhs.height) return 1; 718 return 0; 719 } 720 }; 721 722 SizeCompare s = new SizeCompare(); 723 TreeSet<Size> sortedResolutions = new TreeSet<Size>(s); 724 sortedResolutions.addAll(unsortedSizes); 725 726 mPreviewSizes = new ArrayList<Size>(sortedResolutions); 727 728 ArrayList<VideoSizeNamePair> availableVideoSizes = getVideoSizeNamePairs(id); 729 String[] availableVideoSizeNames = new String[availableVideoSizes.size()]; 730 mVideoSizeIds = new ArrayList<Integer>(); 731 for (int i = 0; i < availableVideoSizes.size(); i++) { 732 availableVideoSizeNames[i] = availableVideoSizes.get(i).getSizeName(); 733 mVideoSizeIds.add(availableVideoSizes.get(i).getSizeId()); 734 } 735 736 mResolutionSpinner.setAdapter( 737 new ArrayAdapter<String>( 738 this, R.layout.cf_format_list_item, availableVideoSizeNames)); 739 740 // Update untested 741 mUntestedCombinations.remove("All combinations for Camera " + id + "\n"); 742 for (int videoSizeId: mVideoSizeIds) { 743 String combination = "Camera " + id + ", " + videoSizeId + "\n"; 744 if (!mTestedCombinations.contains(combination)) { 745 mUntestedCombinations.add(combination); 746 } 747 } 748 749 // Set initial values 750 mCurrentVideoSizeId = mVideoSizeIds.get(0); 751 mNextPreviewSize = matchPreviewRecordSize(); 752 mResolutionSpinner.setSelection(0); 753 754 // Set up correct display orientation 755 CameraInfo info = new CameraInfo(); 756 Camera.getCameraInfo(id, info); 757 int rotation = getWindowManager().getDefaultDisplay().getRotation(); 758 int degrees = 0; 759 switch (rotation) { 760 case Surface.ROTATION_0: degrees = 0; break; 761 case Surface.ROTATION_90: degrees = 90; break; 762 case Surface.ROTATION_180: degrees = 180; break; 763 case Surface.ROTATION_270: degrees = 270; break; 764 } 765 766 if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 767 mVideoRotation = (info.orientation + degrees) % 360; 768 mPreviewRotation = (360 - mVideoRotation) % 360; // compensate the mirror 769 } else { // back-facing 770 mVideoRotation = (info.orientation - degrees + 360) % 360; 771 mPreviewRotation = mVideoRotation; 772 } 773 if (mPreviewRotation != 0 && mPreviewRotation != 180) { 774 Log.w(TAG, 775 "Display orientation correction is not 0 or 180, as expected!"); 776 } 777 778 mCamera.setDisplayOrientation(mPreviewRotation); 779 780 // Start up preview if display is ready 781 if (mPreviewTexture != null) { 782 startPreview(); 783 } 784 } 785 shutdownCamera()786 private void shutdownCamera() { 787 if (mCamera != null) { 788 mCamera.setPreviewCallback(null); 789 mCamera.stopPreview(); 790 mCamera.release(); 791 mCamera = null; 792 } 793 } 794 795 /** 796 * starts capturing and drawing frames on screen 797 */ startPreview()798 private void startPreview() { 799 800 mCamera.stopPreview(); 801 802 Matrix transform = new Matrix(); 803 float widthRatio = mNextPreviewSize.width / (float)mPreviewTexWidth; 804 float heightRatio = mNextPreviewSize.height / (float)mPreviewTexHeight; 805 if (VERBOSE) { 806 Log.v(TAG, "startPreview: widthRatio=" + widthRatio + " " + "heightRatio=" + 807 heightRatio); 808 } 809 810 if (heightRatio < widthRatio) { 811 transform.setScale(1, heightRatio / widthRatio); 812 transform.postTranslate(0, 813 mPreviewTexHeight * (1 - heightRatio / widthRatio) / 2); 814 if (VERBOSE) { 815 Log.v(TAG, "startPreview: shrink vertical by " + heightRatio / widthRatio); 816 } 817 } else { 818 transform.setScale(widthRatio / heightRatio, 1); 819 transform.postTranslate(mPreviewTexWidth * (1 - widthRatio / heightRatio) / 2, 0); 820 if (VERBOSE) { 821 Log.v(TAG, "startPreview: shrink horizontal by " + widthRatio / heightRatio); 822 } 823 } 824 825 mPreviewView.setTransform(transform); 826 827 mPreviewSize = mNextPreviewSize; 828 829 Camera.Parameters p = mCamera.getParameters(); 830 p.setPreviewSize(mPreviewSize.width, mPreviewSize.height); 831 mCamera.setParameters(p); 832 833 try { 834 mCamera.setPreviewTexture(mPreviewTexture); 835 if (mPreviewTexture == null) { 836 Log.e(TAG, "preview texture is null."); 837 } 838 if (VERBOSE) { 839 Log.v(TAG, "startPreview: set preview texture in startPreview"); 840 } 841 mCamera.startPreview(); 842 if (VERBOSE) { 843 Log.v(TAG, "startPreview: started preview in startPreview"); 844 } 845 } catch (IOException ioe) { 846 Log.e(TAG, "Unable to start up preview", ioe); 847 // Show a dialog box to tell user test failed 848 failTest("Unable to start preview."); 849 } 850 } 851 failTest(String failMessage)852 private void failTest(String failMessage) { 853 DialogInterface.OnClickListener dialogClickListener = 854 new DialogInterface.OnClickListener() { 855 @Override 856 public void onClick(DialogInterface dialog, int which) { 857 switch (which) { 858 case DialogInterface.BUTTON_POSITIVE: 859 setTestResultAndFinish(/* passed */false); 860 break; 861 case DialogInterface.BUTTON_NEGATIVE: 862 break; 863 } 864 } 865 }; 866 867 AlertDialog.Builder builder = new AlertDialog.Builder(CameraVideoActivity.this); 868 builder.setMessage(getString(R.string.dialog_fail_test) + ". " + failMessage) 869 .setPositiveButton(R.string.fail_quit, dialogClickListener) 870 .setNegativeButton(R.string.cancel, dialogClickListener) 871 .show(); 872 } 873 isExternalCamera(int cameraId)874 private boolean isExternalCamera(int cameraId) { 875 CameraManager manager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE); 876 try { 877 String cameraIdStr = manager.getCameraIdList()[cameraId]; 878 CameraCharacteristics characteristics = 879 manager.getCameraCharacteristics(cameraIdStr); 880 881 if (characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) == 882 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL) { 883 // External camera doesn't support FOV informations 884 return true; 885 } 886 } catch (CameraAccessException e) { 887 Toast.makeText(this, "Could not access camera " + cameraId + 888 ": " + e.getMessage(), Toast.LENGTH_LONG).show(); 889 } 890 return false; 891 } 892 893 } 894