/* * 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 { 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"); } } }