/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.cts.verifier.camera.video; import android.app.AlertDialog; import android.content.DialogInterface; import android.graphics.Matrix; import android.graphics.SurfaceTexture; import android.hardware.Camera; import android.hardware.Camera.CameraInfo; import android.hardware.Camera.Size; import android.hardware.cts.helpers.CameraUtils; import android.media.CamcorderProfile; import android.media.MediaPlayer; import android.media.MediaRecorder; import android.os.Bundle; import android.os.Handler; import android.text.method.ScrollingMovementMethod; import android.util.Log; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ImageButton; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import android.widget.VideoView; import com.android.cts.verifier.PassFailButtons; import com.android.cts.verifier.R; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Optional; import java.util.TreeSet; /** * Tests for manual verification of camera video capture */ public class CameraVideoActivity extends PassFailButtons.Activity implements TextureView.SurfaceTextureListener { private static final String TAG = "CtsCameraVideo"; private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); private static final int MEDIA_TYPE_IMAGE = 1; private static final int MEDIA_TYPE_VIDEO = 2; private static final int VIDEO_LENGTH = 3000; // in ms private TextureView mPreviewView; private SurfaceTexture mPreviewTexture; private int mPreviewTexWidth; private int mPreviewTexHeight; private int mPreviewRotation; private int mVideoRotation; private VideoView mPlaybackView; private Spinner mCameraSpinner; private Spinner mResolutionSpinner; private int mCurrentCameraId = -1; private Camera mCamera; private boolean mIsExternalCamera; private int mVideoFrameRate = -1; private MediaRecorder mMediaRecorder; private List mPreviewSizes; private Size mNextPreviewSize; private Size mPreviewSize; private List mVideoSizeIds; private List mVideoSizeNames; private int mCurrentVideoSizeId; private String mCurrentVideoSizeName; private boolean isRecording = false; private boolean isPlayingBack = false; private Button captureButton; private ImageButton mPassButton; private ImageButton mFailButton; private TextView mStatusLabel; private TreeSet mTestedCombinations = new TreeSet<>(COMPARATOR); private TreeSet mUntestedCombinations = new TreeSet<>(COMPARATOR); private TreeSet mUntestedCameras = new TreeSet<>(); private File outputVideoFile; private class CameraCombination { private final int mCameraIndex; private final int mVideoSizeIdIndex; private final String mVideoSizeName; private CameraCombination( int cameraIndex, int videoSizeIdIndex, String videoSizeName) { this.mCameraIndex = cameraIndex; this.mVideoSizeIdIndex = videoSizeIdIndex; this.mVideoSizeName = videoSizeName; } @Override public String toString() { return String.format("Camera %d, %s", mCameraIndex, mVideoSizeName); } } private static final Comparator COMPARATOR = Comparator.comparing(c -> c.mCameraIndex) .thenComparing(c -> c.mVideoSizeIdIndex); /** * @see #MEDIA_TYPE_IMAGE * @see #MEDIA_TYPE_VIDEO */ private File getOutputMediaFile(int type) { File mediaStorageDir = new File(getExternalFilesDir(null), TAG); if (mediaStorageDir == null) { Log.e(TAG, "failed to retrieve external files directory"); return null; } if (!mediaStorageDir.exists()) { if (!mediaStorageDir.mkdirs()) { Log.d(TAG, "failed to create directory"); return null; } } String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); File mediaFile; if (type == MEDIA_TYPE_IMAGE) { mediaFile = new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg"); } else if (type == MEDIA_TYPE_VIDEO) { mediaFile = new File(mediaStorageDir.getPath() + File.separator + "VID_" + timeStamp + ".mp4"); if (VERBOSE) { Log.v(TAG, "getOutputMediaFile: output file " + mediaFile.getPath()); } } else { return null; } return mediaFile; } private static final int BIT_RATE_720P = 8000000; private static final int BIT_RATE_MIN = 64000; private static final int BIT_RATE_MAX = BIT_RATE_720P; private int getVideoBitRate(Camera.Size sz) { int rate = BIT_RATE_720P; float scaleFactor = sz.height * sz.width / (float)(1280 * 720); rate = (int)(rate * scaleFactor); // Clamp to the MIN, MAX range. return Math.max(BIT_RATE_MIN, Math.min(BIT_RATE_MAX, rate)); } private int getVideoFrameRate() { return mVideoFrameRate; } private void setVideoFrameRate(int videoFrameRate) { mVideoFrameRate = videoFrameRate; } private boolean prepareVideoRecorder() { mMediaRecorder = new MediaRecorder(); // Step 1: unlock and set camera to MediaRecorder mCamera.unlock(); mMediaRecorder.setCamera(mCamera); // Step 2: set sources mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); // Step 3: set a CamcorderProfile if (mIsExternalCamera) { Camera.Size recordSize = null; switch (mCurrentVideoSizeId) { case CamcorderProfile.QUALITY_QCIF: recordSize = mCamera.new Size(176, 144); break; case CamcorderProfile.QUALITY_QVGA: recordSize = mCamera.new Size(320, 240); break; case CamcorderProfile.QUALITY_CIF: recordSize = mCamera.new Size(352, 288); break; case CamcorderProfile.QUALITY_480P: recordSize = mCamera.new Size(720, 480); break; case CamcorderProfile.QUALITY_720P: recordSize = mCamera.new Size(1280, 720); break; default: String msg = "Unknown CamcorderProfile: " + mCurrentVideoSizeId; Log.e(TAG, msg); releaseMediaRecorder(); throw new AssertionError(msg); } mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); mMediaRecorder.setVideoEncodingBitRate(getVideoBitRate(recordSize)); mMediaRecorder.setVideoSize(recordSize.width, recordSize.height); mMediaRecorder.setVideoFrameRate(getVideoFrameRate()); } else { mMediaRecorder.setProfile(CamcorderProfile.get(mCurrentCameraId, mCurrentVideoSizeId)); } // Step 4: set output file outputVideoFile = getOutputMediaFile(MEDIA_TYPE_VIDEO); mMediaRecorder.setOutputFile(outputVideoFile.toString()); // Step 5: set preview output // This is not necessary since preview has been taken care of // Step 6: set orientation hint mMediaRecorder.setOrientationHint(mVideoRotation); // Step 7: prepare configured MediaRecorder try { mMediaRecorder.prepare(); } catch (IOException e) { Log.e(TAG, "IOException preparing MediaRecorder: ", e); releaseMediaRecorder(); throw new AssertionError(e); } mMediaRecorder.setOnErrorListener( new MediaRecorder.OnErrorListener() { @Override public void onError(MediaRecorder mr, int what, int extra) { if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) { Log.e(TAG, "unknown error in media recorder, error: " + extra); } else { Log.e(TAG, "media recorder server died, error: " + extra); } failTest("Media recorder error."); } }); if (VERBOSE) { Log.v(TAG, "prepareVideoRecorder: prepared configured MediaRecorder"); } return true; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.camera_video); setPassFailButtonClickListeners(); setInfoResources(R.string.camera_video, R.string.video_info, /*viewId*/-1); mPreviewView = (TextureView) findViewById(R.id.video_capture); mPlaybackView = (VideoView) findViewById(R.id.video_playback); mPlaybackView.setOnCompletionListener(mPlaybackViewListener); captureButton = (Button) findViewById(R.id.record_button); mPassButton = (ImageButton) findViewById(R.id.pass_button); mFailButton = (ImageButton) findViewById(R.id.fail_button); mPassButton.setEnabled(false); mFailButton.setEnabled(true); mPreviewView.setSurfaceTextureListener(this); int numCameras = Camera.getNumberOfCameras(); String[] cameraNames = new String[numCameras]; for (int i = 0; i < numCameras; i++) { cameraNames[i] = "Camera " + i; mUntestedCameras.add("All combinations for Camera " + i + "\n"); } if (VERBOSE) { Log.v(TAG, "onCreate: number of cameras=" + numCameras); } mCameraSpinner = (Spinner) findViewById(R.id.cameras_selection); mCameraSpinner.setAdapter( new ArrayAdapter( this, R.layout.camera_list_item, cameraNames)); mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener); mResolutionSpinner = (Spinner) findViewById(R.id.resolution_selection); mResolutionSpinner.setOnItemSelectedListener(mResolutionSelectedListener); mStatusLabel = (TextView) findViewById(R.id.status_label); Button mNextButton = (Button) findViewById(R.id.next_button); mNextButton.setOnClickListener(v -> { setUntestedCombination(); if (VERBOSE) { Log.v(TAG, "onClick: mCurrentVideoSizeId = " + mCurrentVideoSizeId + " " + mCurrentVideoSizeName); Log.v(TAG, "onClick: setting preview size " + mNextPreviewSize.width + "x" + mNextPreviewSize.height); } startPreview(); if (VERBOSE) { Log.v(TAG, "onClick: started new preview"); } captureButton.performClick(); }); } /** * Set an untested combination of the current camera and video size. * Triggered by next button click. */ private void setUntestedCombination() { Optional combination = mUntestedCombinations.stream().filter( c -> c.mCameraIndex == mCurrentCameraId).findFirst(); if (!combination.isPresent()) { Toast.makeText(this, "All Camera " + mCurrentCameraId + " tests are done.", Toast.LENGTH_SHORT).show(); return; } // There is untested combination for the current camera, set the next untested combination. int mNextVideoSizeIdIndex = combination.get().mVideoSizeIdIndex; mCurrentVideoSizeId = mVideoSizeIds.get(mNextVideoSizeIdIndex); mCurrentVideoSizeName = mVideoSizeNames.get(mNextVideoSizeIdIndex); mNextPreviewSize = matchPreviewRecordSize(); mResolutionSpinner.setSelection(mNextVideoSizeIdIndex); } @Override public void onResume() { super.onResume(); setUpCamera(mCameraSpinner.getSelectedItemPosition()); if (VERBOSE) { Log.v(TAG, "onResume: camera has been setup"); } setUpCaptureButton(); if (VERBOSE) { Log.v(TAG, "onResume: captureButton has been setup"); } } @Override public void onPause() { super.onPause(); releaseMediaRecorder(); shutdownCamera(); mPreviewTexture = null; } private MediaPlayer.OnCompletionListener mPlaybackViewListener = new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { isPlayingBack = false; mPlaybackView.stopPlayback(); captureButton.setEnabled(true); mStatusLabel.setMovementMethod(new ScrollingMovementMethod()); StringBuilder progress = new StringBuilder(); progress.append(getResources().getString(R.string.status_ready)); progress.append("\n---- Progress ----\n"); progress.append(getTestDetails()); mStatusLabel.setText(progress.toString()); } }; private void releaseMediaRecorder() { if (mMediaRecorder != null) { mMediaRecorder.reset(); mMediaRecorder.release(); mMediaRecorder = null; mCamera.lock(); // check here, lock camera for later use } } @Override public String getTestDetails() { StringBuilder reportBuilder = new StringBuilder(); reportBuilder.append("Tested combinations:\n"); for (CameraCombination combination: mTestedCombinations) { reportBuilder.append(combination); reportBuilder.append("\n"); } reportBuilder.append("Untested combinations:\n"); for (String untestedCam : mUntestedCameras) { reportBuilder.append(untestedCam); } for (CameraCombination combination: mUntestedCombinations) { reportBuilder.append(combination); reportBuilder.append("\n"); } return reportBuilder.toString(); } @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { mPreviewTexture = surface; mPreviewTexWidth = width; mPreviewTexHeight = height; if (mCamera != null) { startPreview(); } } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { // Ignored, Camera does all the work for us } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { // Invoked every time there's a new Camera preview frame } private AdapterView.OnItemSelectedListener mCameraSpinnerListener = new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int pos, long id) { if (mCurrentCameraId != pos) { setUpCamera(pos); } } @Override public void onNothingSelected(AdapterView parent) { // Intentionally left blank } }; private AdapterView.OnItemSelectedListener mResolutionSelectedListener = new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { if (mVideoSizeIds.get(position) != mCurrentVideoSizeId) { mCurrentVideoSizeId = mVideoSizeIds.get(position); mCurrentVideoSizeName = mVideoSizeNames.get(position); if (VERBOSE) { Log.v(TAG, "onItemSelected: mCurrentVideoSizeId = " + mCurrentVideoSizeId + " " + mCurrentVideoSizeName); } mNextPreviewSize = matchPreviewRecordSize(); if (VERBOSE) { Log.v(TAG, "onItemSelected: setting preview size " + mNextPreviewSize.width + "x" + mNextPreviewSize.height); } startPreview(); if (VERBOSE) { Log.v(TAG, "onItemSelected: started new preview"); } } } @Override public void onNothingSelected(AdapterView parent) { // Intentionally left blank } }; private void setUpCaptureButton() { captureButton.setOnClickListener ( new View.OnClickListener() { @Override public void onClick(View V) { if ((!isRecording) && (!isPlayingBack)) { if (prepareVideoRecorder()) { mMediaRecorder.start(); if (VERBOSE) { Log.v(TAG, "onClick: started mMediaRecorder"); } isRecording = true; captureButton.setEnabled(false); mStatusLabel.setText(getResources() .getString(R.string.status_recording)); } else { releaseMediaRecorder(); Log.e(TAG, "media recorder cannot be set up"); failTest("Unable to set up media recorder."); } Handler h = new Handler(); Runnable mDelayedPreview = new Runnable() { @Override public void run() { mMediaRecorder.stop(); releaseMediaRecorder(); mPlaybackView.setVideoPath(outputVideoFile.getPath()); // Crop playback vertically if necessary float videoWidth = (float) mNextPreviewSize.width; float videoHeight = (float) mNextPreviewSize.height; if (mVideoRotation == 90 || mVideoRotation == 270) { videoWidth = (float) mNextPreviewSize.height; videoHeight = (float) mNextPreviewSize.width; } float potentialHeight = mPlaybackView.getWidth() * (videoHeight / videoWidth); if (potentialHeight > mPreviewTexHeight) { // Use mPreviewTexHeight as a reference as // mPlaybackView.getHeight() is from the previous playback float scaleY = potentialHeight / (float) mPreviewTexHeight; mPlaybackView.setScaleY(scaleY); } else { mPlaybackView.setScaleY(1.0f); } mPlaybackView.start(); isRecording = false; isPlayingBack = true; mStatusLabel.setText(getResources() .getString(R.string.status_playback)); int resIdx = mResolutionSpinner.getSelectedItemPosition(); CameraCombination combination = new CameraCombination( mCurrentCameraId, resIdx, mVideoSizeNames.get(resIdx)); mUntestedCombinations.remove(combination); mTestedCombinations.add(combination); if (mUntestedCombinations.isEmpty() && mUntestedCameras.isEmpty()) { mPassButton.setEnabled(true); if (VERBOSE) { Log.v(TAG, "run: test success"); } } } }; h.postDelayed(mDelayedPreview, VIDEO_LENGTH); } } } ); } private class VideoSizeNamePair { private int sizeId; private String sizeName; public VideoSizeNamePair(int id, String name) { sizeId = id; sizeName = name; } public int getSizeId() { return sizeId; } public String getSizeName() { return sizeName; } } private ArrayList getVideoSizeNamePairs(int cameraId) { int[] qualityArray = { CamcorderProfile.QUALITY_LOW, CamcorderProfile.QUALITY_HIGH, CamcorderProfile.QUALITY_QCIF, // 176x144 CamcorderProfile.QUALITY_QVGA, // 320x240 CamcorderProfile.QUALITY_CIF, // 352x288 CamcorderProfile.QUALITY_480P, // 720x480 CamcorderProfile.QUALITY_720P, // 1280x720 CamcorderProfile.QUALITY_1080P, // 1920x1080 or 1920x1088 CamcorderProfile.QUALITY_2160P }; final Camera.Size skip = mCamera.new Size(-1, -1); Camera.Size[] videoSizeArray = { skip, skip, mCamera.new Size(176, 144), mCamera.new Size(320, 240), mCamera.new Size(352, 288), mCamera.new Size(720, 480), mCamera.new Size(1280, 720), skip, skip }; String[] nameArray = { "LOW", "HIGH", "QCIF", "QVGA", "CIF", "480P", "720P", "1080P", "2160P" }; ArrayList availableSizes = new ArrayList (); Camera.Parameters p = mCamera.getParameters(); List supportedVideoSizes = p.getSupportedVideoSizes(); for (int i = 0; i < qualityArray.length; i++) { if (mIsExternalCamera) { Camera.Size videoSz = videoSizeArray[i]; if (videoSz.equals(skip)) { continue; } if (supportedVideoSizes.contains(videoSz)) { VideoSizeNamePair pair = new VideoSizeNamePair(qualityArray[i], nameArray[i]); availableSizes.add(pair); } } else { if (CamcorderProfile.hasProfile(cameraId, qualityArray[i])) { VideoSizeNamePair pair = new VideoSizeNamePair(qualityArray[i], nameArray[i]); availableSizes.add(pair); } } } return availableSizes; } static class ResolutionQuality { private int videoSizeId; private int width; private int height; public ResolutionQuality() { // intentionally left blank } public ResolutionQuality(int newSizeId, int newWidth, int newHeight) { videoSizeId = newSizeId; width = newWidth; height = newHeight; } } private Size findRecordSize(int cameraId) { int[] possibleQuality = { CamcorderProfile.QUALITY_LOW, CamcorderProfile.QUALITY_HIGH, CamcorderProfile.QUALITY_QCIF, CamcorderProfile.QUALITY_QVGA, CamcorderProfile.QUALITY_CIF, CamcorderProfile.QUALITY_480P, CamcorderProfile.QUALITY_720P, CamcorderProfile.QUALITY_1080P, CamcorderProfile.QUALITY_2160P }; final Camera.Size skip = mCamera.new Size(-1, -1); Camera.Size[] videoSizeArray = { skip, skip, mCamera.new Size(176, 144), mCamera.new Size(320, 240), mCamera.new Size(352, 288), mCamera.new Size(720, 480), mCamera.new Size(1280, 720), skip, skip }; ArrayList qualityList = new ArrayList(); Camera.Parameters p = mCamera.getParameters(); List supportedVideoSizes = p.getSupportedVideoSizes(); for (int i = 0; i < possibleQuality.length; i++) { if (mIsExternalCamera) { Camera.Size videoSz = videoSizeArray[i]; if (videoSz.equals(skip)) { continue; } if (supportedVideoSizes.contains(videoSz)) { qualityList.add(new ResolutionQuality(possibleQuality[i], videoSz.width, videoSz.height)); } } else { if (CamcorderProfile.hasProfile(cameraId, possibleQuality[i])) { CamcorderProfile profile = CamcorderProfile.get(cameraId, possibleQuality[i]); qualityList.add(new ResolutionQuality(possibleQuality[i], profile.videoFrameWidth, profile.videoFrameHeight)); } } } Size recordSize = null; for (int i = 0; i < qualityList.size(); i++) { if (mCurrentVideoSizeId == qualityList.get(i).videoSizeId) { recordSize = mCamera.new Size(qualityList.get(i).width, qualityList.get(i).height); break; } } if (recordSize == null) { Log.e(TAG, "findRecordSize: did not find a match"); failTest("Cannot find video size"); } return recordSize; } // Match preview size with current recording size mCurrentVideoSizeId private Size matchPreviewRecordSize() { Size recordSize = findRecordSize(mCurrentCameraId); Size matchedSize = null; // First try to find exact match in size for (int i = 0; i < mPreviewSizes.size(); i++) { if (mPreviewSizes.get(i).equals(recordSize)) { matchedSize = mCamera.new Size(recordSize.width, recordSize.height); break; } } // Second try to find same ratio in size if (matchedSize == null) { for (int i = mPreviewSizes.size() - 1; i >= 0; i--) { if (mPreviewSizes.get(i).width * recordSize.height == mPreviewSizes.get(i).height * recordSize.width) { matchedSize = mCamera.new Size(mPreviewSizes.get(i).width, mPreviewSizes.get(i).height); break; } } } //Third try to find one with similar if not the same apect ratio if (matchedSize == null) { for (int i = mPreviewSizes.size() - 1; i >= 0; i--) { if (Math.abs((float)mPreviewSizes.get(i).width * recordSize.height / mPreviewSizes.get(i).height / recordSize.width - 1) < 0.12) { matchedSize = mCamera.new Size(mPreviewSizes.get(i).width, mPreviewSizes.get(i).height); break; } } } // Last resort, just use the first preview size if (matchedSize == null) { matchedSize = mCamera.new Size(mPreviewSizes.get(0).width, mPreviewSizes.get(0).height); } if (VERBOSE) { Log.v(TAG, "matchPreviewRecordSize " + matchedSize.width + "x" + matchedSize.height); } return matchedSize; } private void setUpCamera(int id) { shutdownCamera(); mCurrentCameraId = id; mIsExternalCamera = isExternalCamera(id); try { mCamera = Camera.open(id); } catch (Exception e) { Log.e(TAG, "camera is not available", e); failTest("camera not available" + e.getMessage()); return; } Camera.Parameters p = mCamera.getParameters(); if (VERBOSE) { Log.v(TAG, "setUpCamera: setUpCamera got camera parameters"); } // Get preview resolutions List unsortedSizes = p.getSupportedPreviewSizes(); class SizeCompare implements Comparator { @Override public int compare(Size lhs, Size rhs) { if (lhs.width < rhs.width) return -1; if (lhs.width > rhs.width) return 1; if (lhs.height < rhs.height) return -1; if (lhs.height > rhs.height) return 1; return 0; } }; if (mIsExternalCamera) { setVideoFrameRate(p.getPreviewFrameRate()); } SizeCompare s = new SizeCompare(); TreeSet sortedResolutions = new TreeSet(s); sortedResolutions.addAll(unsortedSizes); mPreviewSizes = new ArrayList(sortedResolutions); ArrayList availableVideoSizes = getVideoSizeNamePairs(id); String[] availableVideoSizeNames = new String[availableVideoSizes.size()]; mVideoSizeIds = new ArrayList(); mVideoSizeNames = new ArrayList(); for (int i = 0; i < availableVideoSizes.size(); i++) { availableVideoSizeNames[i] = availableVideoSizes.get(i).getSizeName(); mVideoSizeIds.add(availableVideoSizes.get(i).getSizeId()); mVideoSizeNames.add(availableVideoSizeNames[i]); } mResolutionSpinner.setAdapter( new ArrayAdapter( this, R.layout.camera_list_item, availableVideoSizeNames)); // Update untested mUntestedCameras.remove("All combinations for Camera " + id + "\n"); for (int videoSizeIdIndex = 0; videoSizeIdIndex < mVideoSizeIds.size(); videoSizeIdIndex++) { CameraCombination combination = new CameraCombination( id, videoSizeIdIndex, mVideoSizeNames.get(videoSizeIdIndex)); if (!mTestedCombinations.contains(combination)) { mUntestedCombinations.add(combination); } } // Set initial values mCurrentVideoSizeId = mVideoSizeIds.get(0); mCurrentVideoSizeName = mVideoSizeNames.get(0); mNextPreviewSize = matchPreviewRecordSize(); mResolutionSpinner.setSelection(0); // Set up correct display orientation CameraInfo info = new CameraInfo(); Camera.getCameraInfo(id, info); int rotation = getWindowManager().getDefaultDisplay().getRotation(); int degrees = 0; switch (rotation) { case Surface.ROTATION_0: degrees = 0; break; case Surface.ROTATION_90: degrees = 90; break; case Surface.ROTATION_180: degrees = 180; break; case Surface.ROTATION_270: degrees = 270; break; } if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { mVideoRotation = (info.orientation + degrees) % 360; mPreviewRotation = (360 - mVideoRotation) % 360; // compensate the mirror } else { // back-facing mVideoRotation = (info.orientation - degrees + 360) % 360; mPreviewRotation = mVideoRotation; } mCamera.setDisplayOrientation(mPreviewRotation); // Start up preview if display is ready if (mPreviewTexture != null) { startPreview(); } } private void shutdownCamera() { if (mCamera != null) { mCamera.setPreviewCallback(null); mCamera.stopPreview(); mCamera.release(); mCamera = null; } } /** * starts capturing and drawing frames on screen */ private void startPreview() { mCamera.stopPreview(); Matrix transform = new Matrix(); float widthRatio = mNextPreviewSize.width / (float)mPreviewTexWidth; float heightRatio = mNextPreviewSize.height / (float)mPreviewTexHeight; float scaledWidth = (float) mPreviewTexWidth; float scaledHeight = (float) mPreviewTexHeight; if (VERBOSE) { Log.v(TAG, "startPreview: widthRatio=" + widthRatio + " " + "heightRatio=" + heightRatio); } if (heightRatio < widthRatio) { scaledHeight = mPreviewTexHeight * (heightRatio / widthRatio); transform.setScale(1, heightRatio / widthRatio); transform.postTranslate(0, (mPreviewTexHeight - scaledHeight) / 2); if (VERBOSE) { Log.v(TAG, "startPreview: shrink vertical by " + heightRatio / widthRatio); } } else { scaledWidth = mPreviewTexWidth * (widthRatio / heightRatio); transform.setScale(widthRatio / heightRatio, 1); transform.postTranslate((mPreviewTexWidth - scaledWidth) / 2, 0); if (VERBOSE) { Log.v(TAG, "startPreview: shrink horizontal by " + widthRatio / heightRatio); } } if (mPreviewRotation == 90 || mPreviewRotation == 270) { float scaledAspect = scaledWidth / scaledHeight; float previewAspect = (float) mNextPreviewSize.width / (float) mNextPreviewSize.height; transform.postScale(1.0f, scaledAspect * previewAspect, (float) mPreviewTexWidth / 2, (float) mPreviewTexHeight / 2); } mPreviewView.setTransform(transform); mPreviewSize = mNextPreviewSize; Camera.Parameters p = mCamera.getParameters(); p.setPreviewSize(mPreviewSize.width, mPreviewSize.height); mCamera.setParameters(p); try { mCamera.setPreviewTexture(mPreviewTexture); if (mPreviewTexture == null) { Log.e(TAG, "preview texture is null."); } if (VERBOSE) { Log.v(TAG, "startPreview: set preview texture in startPreview"); } mCamera.startPreview(); if (VERBOSE) { Log.v(TAG, "startPreview: started preview in startPreview"); } } catch (IOException ioe) { Log.e(TAG, "Unable to start up preview", ioe); // Show a dialog box to tell user test failed failTest("Unable to start preview."); } } private void failTest(String failMessage) { DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { switch (which) { case DialogInterface.BUTTON_POSITIVE: setTestResultAndFinish(/* passed */false); break; case DialogInterface.BUTTON_NEGATIVE: break; } } }; AlertDialog.Builder builder = new AlertDialog.Builder(CameraVideoActivity.this); builder.setMessage(getString(R.string.dialog_fail_test) + ". " + failMessage) .setPositiveButton(R.string.fail_quit, dialogClickListener) .setNegativeButton(R.string.cancel, dialogClickListener) .show(); } private boolean isExternalCamera(int cameraId) { try { return CameraUtils.isExternal(this, cameraId); } catch (Exception e) { Toast.makeText(this, "Could not access camera " + cameraId + ": " + e.getMessage(), Toast.LENGTH_LONG).show(); } return false; } }