| /* |
| * Copyright (C) 2009 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.mediaframeworktest.stress; |
| |
| |
| import com.android.mediaframeworktest.MediaFrameworkTest; |
| |
| import java.io.BufferedWriter; |
| import java.io.File; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.Writer; |
| import java.util.concurrent.Semaphore; |
| import java.util.concurrent.TimeUnit; |
| |
| import android.hardware.Camera; |
| import android.media.CamcorderProfile; |
| import android.media.MediaPlayer; |
| import android.media.MediaRecorder; |
| import android.os.Environment; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.test.ActivityInstrumentationTestCase2; |
| import android.test.suitebuilder.annotation.LargeTest; |
| import android.util.Log; |
| import android.view.SurfaceHolder; |
| import com.android.mediaframeworktest.MediaRecorderStressTestRunner; |
| |
| /** |
| * Junit / Instrumentation test case for the media player api |
| */ |
| public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> { |
| |
| private String TAG = "MediaRecorderStressTest"; |
| private MediaRecorder mRecorder; |
| private Camera mCamera; |
| |
| private static final int CAMERA_ID = 0; |
| private static final int NUMBER_OF_TIME_LAPSE_LOOPS = 1; |
| private static final int TIME_LAPSE_PLAYBACK_WAIT_TIME = 30 * 1000; // 30 seconds |
| private static final int USE_TEST_RUNNER_PROFILE = -1; |
| private static final long WAIT_TIMEOUT = 10 * 1000; // 10 seconds |
| private static final String OUTPUT_FILE_EXT = ".3gp"; |
| private static final String MEDIA_STRESS_OUTPUT = "mediaStressOutput.txt"; |
| |
| private final CameraErrorCallback mCameraErrorCallback = new CameraErrorCallback(); |
| private final RecorderErrorCallback mRecorderErrorCallback = new RecorderErrorCallback(); |
| |
| private Handler mHandler; |
| private Thread mLooperThread; |
| private Writer mOutput; |
| |
| public MediaRecorderStressTest() { |
| super("com.android.mediaframeworktest", MediaFrameworkTest.class); |
| } |
| |
| protected void setUp() throws Exception { |
| final Semaphore sem = new Semaphore(0); |
| mLooperThread = new Thread() { |
| @Override |
| public void run() { |
| Log.v(TAG, "starting looper"); |
| Looper.prepare(); |
| mHandler = new Handler(); |
| sem.release(); |
| Looper.loop(); |
| Log.v(TAG, "quit looper"); |
| } |
| }; |
| mLooperThread.start(); |
| if (! sem.tryAcquire(WAIT_TIMEOUT, TimeUnit.MILLISECONDS)) { |
| fail("Failed to start the looper."); |
| } |
| //Insert a 2 second before launching the test activity. This is |
| //the workaround for the race condition of requesting the updated surface. |
| Thread.sleep(2000); |
| getActivity(); |
| super.setUp(); |
| |
| File stressOutFile = new File(String.format("%s/%s", |
| Environment.getExternalStorageDirectory(), MEDIA_STRESS_OUTPUT)); |
| mOutput = new BufferedWriter(new FileWriter(stressOutFile, true)); |
| mOutput.write(this.getName() + "\n"); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| if (mHandler != null) { |
| mHandler.getLooper().quit(); |
| mHandler = null; |
| } |
| if (mLooperThread != null) { |
| mLooperThread.join(WAIT_TIMEOUT); |
| if (mLooperThread.isAlive()) { |
| fail("Failed to stop the looper."); |
| } |
| mLooperThread = null; |
| } |
| mOutput.write("\n\n"); |
| mOutput.close(); |
| super.tearDown(); |
| } |
| |
| private void runOnLooper(final Runnable command) throws InterruptedException { |
| final Semaphore sem = new Semaphore(0); |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| command.run(); |
| } finally { |
| sem.release(); |
| } |
| } |
| }); |
| if (! sem.tryAcquire(WAIT_TIMEOUT, TimeUnit.MILLISECONDS)) { |
| fail("Failed to run the command on the looper."); |
| } |
| } |
| |
| private final class CameraErrorCallback implements android.hardware.Camera.ErrorCallback { |
| public void onError(int error, android.hardware.Camera camera) { |
| fail(String.format("Camera error, code: %d", error)); |
| } |
| } |
| |
| private final class RecorderErrorCallback implements MediaRecorder.OnErrorListener { |
| public void onError(MediaRecorder mr, int what, int extra) { |
| fail(String.format("Media recorder error, code: %d\textra: %d", what, extra)); |
| } |
| } |
| |
| public void validateRecordedVideo(String recordedFile) { |
| try { |
| MediaPlayer mp = new MediaPlayer(); |
| mp.setDataSource(recordedFile); |
| mp.prepare(); |
| int duration = mp.getDuration(); |
| if (duration <= 0){ |
| fail("stressRecordAndPlayback"); |
| } |
| mp.release(); |
| } catch (Exception e) { |
| fail("stressRecordAndPlayback"); |
| } |
| } |
| |
| public void removeRecordedVideo(String fileName){ |
| File video = new File(fileName); |
| Log.v(TAG, "remove recorded video " + fileName); |
| video.delete(); |
| } |
| |
| // Helper method for record & playback testing with different camcorder profiles |
| private void recordVideoAndPlayback(int profile) throws Exception { |
| int iterations; |
| int recordDuration; |
| boolean removeVideo; |
| |
| int videoEncoder; |
| int audioEncoder; |
| int frameRate; |
| int videoWidth; |
| int videoHeight; |
| int bitRate; |
| |
| if (profile != USE_TEST_RUNNER_PROFILE) { |
| assertTrue(String.format("Camera doesn't support profile %d", profile), |
| CamcorderProfile.hasProfile(CAMERA_ID, profile)); |
| CamcorderProfile camcorderProfile = CamcorderProfile.get(CAMERA_ID, profile); |
| videoEncoder = camcorderProfile.videoCodec; |
| audioEncoder = camcorderProfile.audioCodec; |
| frameRate = camcorderProfile.videoFrameRate; |
| videoWidth = camcorderProfile.videoFrameWidth; |
| videoHeight = camcorderProfile.videoFrameHeight; |
| bitRate = camcorderProfile.videoBitRate; |
| } else { |
| videoEncoder = MediaRecorderStressTestRunner.mVideoEncoder; |
| audioEncoder = MediaRecorderStressTestRunner.mAudioEncoder; |
| frameRate = MediaRecorderStressTestRunner.mFrameRate; |
| videoWidth = MediaRecorderStressTestRunner.mVideoWidth; |
| videoHeight = MediaRecorderStressTestRunner.mVideoHeight; |
| bitRate = MediaRecorderStressTestRunner.mBitRate; |
| } |
| iterations = MediaRecorderStressTestRunner.mIterations; |
| recordDuration = MediaRecorderStressTestRunner.mDuration; |
| removeVideo = MediaRecorderStressTestRunner.mRemoveVideo; |
| |
| SurfaceHolder surfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder(); |
| mOutput.write("Total number of loops: " + iterations + "\n"); |
| |
| try { |
| mOutput.write("No of loop: "); |
| for (int i = 0; i < iterations; i++) { |
| String fileName = String.format("%s/temp%d%s", |
| Environment.getExternalStorageDirectory(), i, OUTPUT_FILE_EXT); |
| Log.v(TAG, fileName); |
| |
| runOnLooper(new Runnable() { |
| @Override |
| public void run() { |
| mRecorder = new MediaRecorder(); |
| } |
| }); |
| |
| Log.v(TAG, "iterations : " + iterations); |
| Log.v(TAG, "video encoder : " + videoEncoder); |
| Log.v(TAG, "audio encoder : " + audioEncoder); |
| Log.v(TAG, "frame rate : " + frameRate); |
| Log.v(TAG, "video width : " + videoWidth); |
| Log.v(TAG, "video height : " + videoHeight); |
| Log.v(TAG, "bit rate : " + bitRate); |
| Log.v(TAG, "record duration : " + recordDuration); |
| |
| mRecorder.setOnErrorListener(mRecorderErrorCallback); |
| mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); |
| mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); |
| mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); |
| mRecorder.setOutputFile(fileName); |
| mRecorder.setVideoFrameRate(frameRate); |
| mRecorder.setVideoSize(videoWidth, videoHeight); |
| mRecorder.setVideoEncoder(videoEncoder); |
| mRecorder.setAudioEncoder(audioEncoder); |
| mRecorder.setVideoEncodingBitRate(bitRate); |
| |
| Log.v(TAG, "mediaRecorder setPreview"); |
| mRecorder.setPreviewDisplay(surfaceHolder.getSurface()); |
| mRecorder.prepare(); |
| mRecorder.start(); |
| Thread.sleep(recordDuration); |
| Log.v(TAG, "Before stop"); |
| mRecorder.stop(); |
| mRecorder.release(); |
| |
| //start the playback |
| MediaPlayer mp = new MediaPlayer(); |
| mp.setDataSource(fileName); |
| mp.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder()); |
| mp.prepare(); |
| mp.start(); |
| Thread.sleep(recordDuration); |
| mp.release(); |
| validateRecordedVideo(fileName); |
| if (removeVideo) { |
| removeRecordedVideo(fileName); |
| } |
| if (i == 0) { |
| mOutput.write(i + 1); |
| } else { |
| mOutput.write(String.format(", %d", (i + 1))); |
| } |
| } |
| } catch (Exception e) { |
| Log.e(TAG, e.toString()); |
| fail("Record and playback"); |
| } |
| } |
| |
| // Record and playback stress test @ 1080P quality |
| @LargeTest |
| public void testStressRecordVideoAndPlayback1080P() throws Exception { |
| recordVideoAndPlayback(CamcorderProfile.QUALITY_1080P); |
| } |
| |
| // Record and playback stress test @ 720P quality |
| @LargeTest |
| public void testStressRecordVideoAndPlayback720P() throws Exception { |
| recordVideoAndPlayback(CamcorderProfile.QUALITY_720P); |
| } |
| |
| // Record and playback stress test @ 480P quality |
| @LargeTest |
| public void testStressRecordVideoAndPlayback480P() throws Exception { |
| recordVideoAndPlayback(CamcorderProfile.QUALITY_480P); |
| } |
| |
| // This test method uses the codec info from the test runner. Use this |
| // for more granular control of video encoding. |
| @LargeTest |
| public void defaultStressRecordVideoAndPlayback() throws Exception { |
| recordVideoAndPlayback(USE_TEST_RUNNER_PROFILE); |
| } |
| |
| // Test case for stressing time lapse |
| @LargeTest |
| public void testStressTimeLapse() throws Exception { |
| SurfaceHolder mSurfaceHolder; |
| mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder(); |
| int recordDuration = MediaRecorderStressTestRunner.mTimeLapseDuration; |
| boolean removeVideo = MediaRecorderStressTestRunner.mRemoveVideo; |
| double captureRate = MediaRecorderStressTestRunner.mCaptureRate; |
| Log.v(TAG, "Start camera time lapse stress:"); |
| mOutput.write("Total number of loops: " + NUMBER_OF_TIME_LAPSE_LOOPS + "\n"); |
| |
| try { |
| for (int i = 0, n = Camera.getNumberOfCameras(); i < n; i++) { |
| mOutput.write("No of loop: camera " + i); |
| for (int j = 0; j < NUMBER_OF_TIME_LAPSE_LOOPS; j++) { |
| String fileName = String.format("%s/temp%d_%d%s", |
| Environment.getExternalStorageDirectory(), i, j, OUTPUT_FILE_EXT); |
| Log.v(TAG, fileName); |
| runOnLooper(new Runnable() { |
| @Override |
| public void run() { |
| mRecorder = new MediaRecorder(); |
| } |
| }); |
| |
| // Set callback |
| mRecorder.setOnErrorListener(mRecorderErrorCallback); |
| |
| // Set video source |
| mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); |
| |
| // Set camcorder profile for time lapse |
| CamcorderProfile profile = |
| CamcorderProfile.get(j, CamcorderProfile.QUALITY_TIME_LAPSE_HIGH); |
| mRecorder.setProfile(profile); |
| |
| // Set the timelapse setting; 0.1 = 10 sec timelapse, 0.5 = 2 sec timelapse, etc |
| // http://developer.android.com/guide/topics/media/camera.html#time-lapse-video |
| mRecorder.setCaptureRate(captureRate); |
| |
| // Set output file |
| mRecorder.setOutputFile(fileName); |
| |
| // Set the preview display |
| Log.v(TAG, "mediaRecorder setPreviewDisplay"); |
| mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); |
| |
| mRecorder.prepare(); |
| mRecorder.start(); |
| Thread.sleep(recordDuration); |
| Log.v(TAG, "Before stop"); |
| mRecorder.stop(); |
| mRecorder.release(); |
| |
| // Start the playback |
| MediaPlayer mp = new MediaPlayer(); |
| mp.setDataSource(fileName); |
| mp.setDisplay(mSurfaceHolder); |
| mp.prepare(); |
| mp.start(); |
| Thread.sleep(TIME_LAPSE_PLAYBACK_WAIT_TIME); |
| mp.release(); |
| validateRecordedVideo(fileName); |
| if (removeVideo) { |
| removeRecordedVideo(fileName); |
| } |
| |
| if (j == 0) { |
| mOutput.write(j + 1); |
| } else { |
| mOutput.write(String.format(", %d", (j + 1))); |
| } |
| } |
| } |
| } catch (IllegalStateException e) { |
| Log.e(TAG, e.toString()); |
| fail("Camera time lapse stress test IllegalStateException"); |
| } catch (IOException e) { |
| Log.e(TAG, e.toString()); |
| fail("Camera time lapse stress test IOException"); |
| } catch (Exception e) { |
| Log.e(TAG, e.toString()); |
| fail("Camera time lapse stress test Exception"); |
| } |
| } |
| } |