/* * Copyright (C) 2008 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.performance; import com.android.mediaframeworktest.MediaFrameworkTest; import com.android.mediaframeworktest.MediaNames; import android.database.sqlite.SQLiteDatabase; import android.hardware.Camera; import android.hardware.Camera.PreviewCallback; import android.media.MediaPlayer; import android.media.MediaRecorder; import android.os.ConditionVariable; import android.os.Looper; import android.os.SystemClock; import android.test.ActivityInstrumentationTestCase; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.Suppress; import android.util.Log; import android.view.SurfaceHolder; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.Writer; import java.io.File; import java.io.FileWriter; import java.io.BufferedWriter; import android.media.MediaMetadataRetriever; import com.android.mediaframeworktest.MediaProfileReader; import android.hardware.Camera.PreviewCallback; /** * Junit / Instrumentation - performance measurement for media player and * recorder */ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase { private String TAG = "MediaPlayerPerformance"; private SQLiteDatabase mDB; private SurfaceHolder mSurfaceHolder = null; private static final int NUM_STRESS_LOOP = 10; private static final int NUM_PLAYBACk_IN_EACH_LOOP = 20; private static final long MEDIA_STRESS_WAIT_TIME = 5000; //5 seconds private static final String MEDIA_MEMORY_OUTPUT = "/sdcard/mediaMemOutput.txt"; private static int mStartMemory = 0; private static int mEndMemory = 0; private static int mStartPid = 0; private static int mEndPid = 0; private Looper mLooper = null; private RawPreviewCallback mRawPreviewCallback = new RawPreviewCallback(); private final ConditionVariable mPreviewDone = new ConditionVariable(); private static int WAIT_FOR_COMMAND_TO_COMPLETE = 10000; // Milliseconds. //the tolerant memory leak private static int ENCODER_LIMIT = 150; private static int DECODER_LIMIT = 150; private static int CAMERA_LIMIT = 80; Camera mCamera; public MediaPlayerPerformance() { super("com.android.mediaframeworktest", MediaFrameworkTest.class); } protected void setUp() throws Exception { super.setUp(); } public void createDB() { mDB = SQLiteDatabase.openOrCreateDatabase("/sdcard/perf.db", null); mDB.execSQL("CREATE TABLE IF NOT EXISTS perfdata (_id INTEGER PRIMARY KEY," + "file TEXT," + "setdatatime LONG," + "preparetime LONG," + "playtime LONG" + ");"); //clean the table before adding new data mDB.execSQL("DELETE FROM perfdata"); } public void audioPlaybackStartupTime(String[] testFile) { long t1 = 0; long t2 = 0; long t3 = 0; long t4 = 0; long setDataSourceDuration = 0; long prepareDuration = 0; long startDuration = 0; long totalSetDataTime = 0; long totalPrepareTime = 0; long totalStartDuration = 0; int numberOfFiles = testFile.length; Log.v(TAG, "File length " + numberOfFiles); for (int k = 0; k < numberOfFiles; k++) { MediaPlayer mp = new MediaPlayer(); try { t1 = SystemClock.uptimeMillis(); FileInputStream fis = new FileInputStream(testFile[k]); FileDescriptor fd = fis.getFD(); mp.setDataSource(fd); fis.close(); t2 = SystemClock.uptimeMillis(); mp.prepare(); t3 = SystemClock.uptimeMillis(); mp.start(); t4 = SystemClock.uptimeMillis(); } catch (Exception e) { Log.v(TAG, e.toString()); } setDataSourceDuration = t2 - t1; prepareDuration = t3 - t2; startDuration = t4 - t3; totalSetDataTime = totalSetDataTime + setDataSourceDuration; totalPrepareTime = totalPrepareTime + prepareDuration; totalStartDuration = totalStartDuration + startDuration; mDB.execSQL("INSERT INTO perfdata (file, setdatatime, preparetime," + " playtime) VALUES (" + '"' + testFile[k] + '"' + ',' + setDataSourceDuration + ',' + prepareDuration + ',' + startDuration + ");"); Log.v(TAG, "File name " + testFile[k]); mp.stop(); mp.release(); } Log.v(TAG, "setDataSource average " + totalSetDataTime / numberOfFiles); Log.v(TAG, "prepare average " + totalPrepareTime / numberOfFiles); Log.v(TAG, "start average " + totalStartDuration / numberOfFiles); } @Suppress public void testStartUpTime() throws Exception { createDB(); audioPlaybackStartupTime(MediaNames.MP3FILES); audioPlaybackStartupTime(MediaNames.AACFILES); //close the database after all transactions if (mDB.isOpen()) { mDB.close(); } } public void wmametadatautility(String[] testFile) { long t1 = 0; long t2 = 0; long sum = 0; long duration = 0; MediaMetadataRetriever retriever = new MediaMetadataRetriever(); String value; for (int i = 0, n = testFile.length; i < n; ++i) { try { t1 = SystemClock.uptimeMillis(); retriever.setDataSource(testFile[i]); value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM); value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST); value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER); value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_GENRE); value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE); value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR); value = retriever .extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER); t2 = SystemClock.uptimeMillis(); duration = t2 - t1; Log.v(TAG, "Time taken = " + duration); sum = sum + duration; } catch (Exception e) { Log.v(TAG, e.getMessage()); } } Log.v(TAG, "Average duration = " + sum / testFile.length); } private void initializeMessageLooper() { final ConditionVariable startDone = new ConditionVariable(); new Thread() { @Override public void run() { Looper.prepare(); Log.v(TAG, "start loopRun"); mLooper = Looper.myLooper(); mCamera = Camera.open(); startDone.open(); Looper.loop(); Log.v(TAG, "initializeMessageLooper: quit."); } }.start(); if (!startDone.block(WAIT_FOR_COMMAND_TO_COMPLETE)) { fail("initializeMessageLooper: start timeout"); } } private void terminateMessageLooper() throws Exception { mLooper.quit(); // Looper.quit() is asynchronous. The looper may still has some // preview callbacks in the queue after quit is called. The preview // callback still uses the camera object (setHasPreviewCallback). // After camera is released, RuntimeException will be thrown from // the method. So we need to join the looper thread here. mLooper.getThread().join(); mCamera.release(); } private final class RawPreviewCallback implements PreviewCallback { public void onPreviewFrame(byte[] rawData, Camera camera) { mPreviewDone.open(); } } private void waitForPreviewDone() { if (!mPreviewDone.block(WAIT_FOR_COMMAND_TO_COMPLETE)) { Log.v(TAG, "waitForPreviewDone: timeout"); } mPreviewDone.close(); } public void stressCameraPreview() { for (int i = 0; i < NUM_PLAYBACk_IN_EACH_LOOP; i++) { try { initializeMessageLooper(); mCamera.setPreviewCallback(mRawPreviewCallback); mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder(); mCamera.setPreviewDisplay(mSurfaceHolder); mCamera.startPreview(); waitForPreviewDone(); Thread.sleep(1000); mCamera.stopPreview(); terminateMessageLooper(); } catch (Exception e) { Log.v(TAG, e.toString()); } } } // Note: This test is to assume the mediaserver's pid is 34 public void mediaStressPlayback(String testFilePath) { for (int i = 0; i < NUM_PLAYBACk_IN_EACH_LOOP; i++) { MediaPlayer mp = new MediaPlayer(); try { mp.setDataSource(testFilePath); mp.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder()); mp.prepare(); mp.start(); Thread.sleep(MEDIA_STRESS_WAIT_TIME); mp.release(); } catch (Exception e) { mp.release(); Log.v(TAG, e.toString()); } } } // Note: This test is to assume the mediaserver's pid is 34 private void stressVideoRecord(int frameRate, int width, int height, int videoFormat, int outFormat, String outFile, boolean videoOnly) { // Video recording for (int i = 0; i < NUM_PLAYBACk_IN_EACH_LOOP; i++) { MediaRecorder mRecorder = new MediaRecorder(); try { if (!videoOnly) { Log.v(TAG, "setAudioSource"); mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); } mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); mRecorder.setOutputFormat(outFormat); Log.v(TAG, "output format " + outFormat); mRecorder.setOutputFile(outFile); mRecorder.setVideoFrameRate(frameRate); mRecorder.setVideoSize(width, height); Log.v(TAG, "setEncoder"); mRecorder.setVideoEncoder(videoFormat); if (!videoOnly) { mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); } mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder(); mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); mRecorder.prepare(); mRecorder.start(); Thread.sleep(MEDIA_STRESS_WAIT_TIME); mRecorder.stop(); mRecorder.release(); } catch (Exception e) { Log.v("record video failed ", e.toString()); mRecorder.release(); } } } public void stressAudioRecord(String filePath) { // This test is only for the short media file for (int i = 0; i < NUM_PLAYBACk_IN_EACH_LOOP; i++) { MediaRecorder mRecorder = new MediaRecorder(); try { mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); mRecorder.setOutputFile(filePath); mRecorder.prepare(); mRecorder.start(); Thread.sleep(MEDIA_STRESS_WAIT_TIME); mRecorder.stop(); mRecorder.release(); } catch (Exception e) { Log.v(TAG, e.toString()); mRecorder.release(); } } } //Write the ps output to the file public void getMemoryWriteToLog(Writer output, int writeCount) { String memusage = null; try { if (writeCount == 0) { mStartMemory = getMediaserverVsize(); output.write("Start memory : " + mStartMemory + "\n"); } memusage = captureMediaserverInfo(); output.write(memusage); if (writeCount == NUM_STRESS_LOOP - 1) { mEndMemory = getMediaserverVsize(); output.write("End Memory :" + mEndMemory + "\n"); } } catch (Exception e) { e.toString(); } } public String captureMediaserverInfo() { String cm = "ps mediaserver"; String memoryUsage = null; int ch; try { Process p = Runtime.getRuntime().exec(cm); InputStream in = p.getInputStream(); StringBuffer sb = new StringBuffer(512); while ((ch = in.read()) != -1) { sb.append((char) ch); } memoryUsage = sb.toString(); } catch (IOException e) { Log.v(TAG, e.toString()); } String[] poList = memoryUsage.split("\r|\n|\r\n"); String memusage = poList[1].concat("\n"); return memusage; } public int getMediaserverPid(){ String memoryUsage = null; int pidvalue = 0; memoryUsage = captureMediaserverInfo(); String[] poList2 = memoryUsage.split("\t|\\s+"); String pid = poList2[1]; pidvalue = Integer.parseInt(pid); Log.v(TAG, "PID = " + pidvalue); return pidvalue; } public int getMediaserverVsize(){ String memoryUsage = captureMediaserverInfo(); String[] poList2 = memoryUsage.split("\t|\\s+"); String vsize = poList2[3]; int vsizevalue = Integer.parseInt(vsize); Log.v(TAG, "VSIZE = " + vsizevalue); return vsizevalue; } public boolean validateMemoryResult(int startPid, int startMemory, Writer output, int limit) throws Exception { // Wait for 10 seconds to make sure the memory settle. Thread.sleep(10000); mEndPid = getMediaserverPid(); int memDiff = mEndMemory - startMemory; if (memDiff < 0) { memDiff = 0; } output.write("The total diff = " + memDiff); output.write("\n\n"); // mediaserver crash if (startPid != mEndPid) { output.write("mediaserver died. Test failed\n"); return false; } // memory leak greter than the tolerant if (memDiff > limit) return false; return true; } @Suppress public void testWmaParseTime() throws Exception { // createDB(); wmametadatautility(MediaNames.WMASUPPORTED); } // Test case 1: Capture the memory usage after every 20 h263 playback @LargeTest public void testH263VideoPlaybackMemoryUsage() throws Exception { boolean memoryResult = false; mStartPid = getMediaserverPid(); File h263MemoryOut = new File(MEDIA_MEMORY_OUTPUT); Writer output = new BufferedWriter(new FileWriter(h263MemoryOut, true)); output.write("H263 Video Playback Only\n"); for (int i = 0; i < NUM_STRESS_LOOP; i++) { mediaStressPlayback(MediaNames.VIDEO_HIGHRES_H263); getMemoryWriteToLog(output, i); } output.write("\n"); memoryResult = validateMemoryResult(mStartPid, mStartMemory, output, DECODER_LIMIT); output.close(); assertTrue("H263 playback memory test", memoryResult); } // Test case 2: Capture the memory usage after every 20 h264 playback @LargeTest public void testH264VideoPlaybackMemoryUsage() throws Exception { boolean memoryResult = false; mStartPid = getMediaserverPid(); File h264MemoryOut = new File(MEDIA_MEMORY_OUTPUT); Writer output = new BufferedWriter(new FileWriter(h264MemoryOut, true)); output.write("H264 Video Playback only\n"); for (int i = 0; i < NUM_STRESS_LOOP; i++) { mediaStressPlayback(MediaNames.VIDEO_H264_AMR); getMemoryWriteToLog(output, i); } output.write("\n"); memoryResult = validateMemoryResult(mStartPid, mStartMemory, output, DECODER_LIMIT); output.close(); assertTrue("H264 playback memory test", memoryResult); } // Test case 3: Capture the memory usage after each 20 WMV playback @LargeTest public void testWMVVideoPlaybackMemoryUsage() throws Exception { boolean memoryResult = false; if (MediaProfileReader.getWMVEnable()){ mStartPid = getMediaserverPid(); File wmvMemoryOut = new File(MEDIA_MEMORY_OUTPUT); Writer output = new BufferedWriter(new FileWriter(wmvMemoryOut, true)); output.write("WMV video playback only\n"); for (int i = 0; i < NUM_STRESS_LOOP; i++) { mediaStressPlayback(MediaNames.VIDEO_WMV); getMemoryWriteToLog(output, i); } output.write("\n"); memoryResult = validateMemoryResult(mStartPid, mStartMemory, output, DECODER_LIMIT); output.close(); assertTrue("wmv playback memory test", memoryResult); } } // Test case 4: Capture the memory usage after every 20 video only recorded @LargeTest public void testH263RecordVideoOnlyMemoryUsage() throws Exception { boolean memoryResult = false; mStartPid = getMediaserverPid(); File videoH263RecordOnlyMemoryOut = new File(MEDIA_MEMORY_OUTPUT); Writer output = new BufferedWriter(new FileWriter(videoH263RecordOnlyMemoryOut, true)); output.write("H263 video record only\n"); for (int i = 0; i < NUM_STRESS_LOOP; i++) { stressVideoRecord(20, 352, 288, MediaRecorder.VideoEncoder.H263, MediaRecorder.OutputFormat.MPEG_4, MediaNames.RECORDED_VIDEO_3GP, true); getMemoryWriteToLog(output, i); } output.write("\n"); memoryResult = validateMemoryResult(mStartPid, mStartMemory, output, ENCODER_LIMIT); output.close(); assertTrue("H263 record only memory test", memoryResult); } // Test case 5: Capture the memory usage after every 20 video only recorded @LargeTest public void testMpeg4RecordVideoOnlyMemoryUsage() throws Exception { boolean memoryResult = false; mStartPid = getMediaserverPid(); File videoMp4RecordOnlyMemoryOut = new File(MEDIA_MEMORY_OUTPUT); Writer output = new BufferedWriter(new FileWriter(videoMp4RecordOnlyMemoryOut, true)); output.write("MPEG4 video record only\n"); for (int i = 0; i < NUM_STRESS_LOOP; i++) { stressVideoRecord(20, 352, 288, MediaRecorder.VideoEncoder.MPEG_4_SP, MediaRecorder.OutputFormat.MPEG_4, MediaNames.RECORDED_VIDEO_3GP, true); getMemoryWriteToLog(output, i); } output.write("\n"); memoryResult = validateMemoryResult(mStartPid, mStartMemory, output, ENCODER_LIMIT); output.close(); assertTrue("mpeg4 record only memory test", memoryResult); } // Test case 6: Capture the memory usage after every 20 video and audio // recorded @LargeTest public void testRecordVidedAudioMemoryUsage() throws Exception { boolean memoryResult = false; mStartPid = getMediaserverPid(); File videoRecordAudioMemoryOut = new File(MEDIA_MEMORY_OUTPUT); Writer output = new BufferedWriter(new FileWriter(videoRecordAudioMemoryOut, true)); output.write("Audio and h263 video record\n"); for (int i = 0; i < NUM_STRESS_LOOP; i++) { stressVideoRecord(20, 352, 288, MediaRecorder.VideoEncoder.H263, MediaRecorder.OutputFormat.MPEG_4, MediaNames.RECORDED_VIDEO_3GP, false); getMemoryWriteToLog(output, i); } output.write("\n"); memoryResult = validateMemoryResult(mStartPid, mStartMemory, output, ENCODER_LIMIT); output.close(); assertTrue("H263 audio video record memory test", memoryResult); } // Test case 7: Capture the memory usage after every 20 audio only recorded @LargeTest public void testRecordAudioOnlyMemoryUsage() throws Exception { boolean memoryResult = false; mStartPid = getMediaserverPid(); File audioOnlyMemoryOut = new File(MEDIA_MEMORY_OUTPUT); Writer output = new BufferedWriter(new FileWriter(audioOnlyMemoryOut, true)); output.write("Audio record only\n"); for (int i = 0; i < NUM_STRESS_LOOP; i++) { stressAudioRecord(MediaNames.RECORDER_OUTPUT); getMemoryWriteToLog(output, i); } output.write("\n"); memoryResult = validateMemoryResult(mStartPid, mStartMemory, output, ENCODER_LIMIT); output.close(); assertTrue("audio record only memory test", memoryResult); } // Test case 8: Capture the memory usage after every 20 camera preview @LargeTest public void testCameraPreviewMemoryUsage() throws Exception { boolean memoryResult = false; mStartPid = getMediaserverPid(); File cameraPreviewMemoryOut = new File(MEDIA_MEMORY_OUTPUT); Writer output = new BufferedWriter(new FileWriter(cameraPreviewMemoryOut, true)); output.write("Camera Preview Only\n"); for (int i = 0; i < NUM_STRESS_LOOP; i++) { stressCameraPreview(); getMemoryWriteToLog(output, i); } output.write("\n"); memoryResult = validateMemoryResult(mStartPid, mStartMemory, output, CAMERA_LIMIT); output.close(); assertTrue("camera preview memory test", memoryResult); } }