blob: f48a280397829078c30b1e6d83b4ac898d9a8a8e [file] [log] [blame]
/*
* 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 android.media.cts;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.media.MediaRecorder;
import android.media.MediaRecorder.OnErrorListener;
import android.media.MediaRecorder.OnInfoListener;
import android.media.MediaMetadataRetriever;
import android.os.Environment;
import android.test.ActivityInstrumentationTestCase2;
import android.test.UiThreadTest;
import android.view.Surface;
import android.util.Log;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaStubActivity> {
private final String TAG = "MediaRecorderTest";
private final String OUTPUT_PATH;
private final String OUTPUT_PATH2;
private static final float TOLERANCE = 0.0002f;
private static final int RECORD_TIME = 3000;
private static final int VIDEO_WIDTH = 176;
private static final int VIDEO_HEIGHT = 144;
private static final int VIDEO_BIT_RATE_IN_BPS = 128000;
private static final double VIDEO_TIMELAPSE_CAPTURE_RATE_FPS = 1.0;
private static final int AUDIO_BIT_RATE_IN_BPS = 12200;
private static final int AUDIO_NUM_CHANNELS = 1;
private static final int AUDIO_SAMPLE_RATE_HZ = 8000;
private static final long MAX_FILE_SIZE = 5000;
private static final int MAX_DURATION_MSEC = 200;
private static final float LATITUDE = 0.0000f;
private static final float LONGITUDE = -180.0f;
private boolean mOnInfoCalled;
private boolean mOnErrorCalled;
private File mOutFile;
private File mOutFile2;
private Camera mCamera;
/*
* InstrumentationTestRunner.onStart() calls Looper.prepare(), which creates a looper
* for the current thread. However, since we don't actually call loop() in the test,
* any messages queued with that looper will never be consumed. We instantiate the recorder
* in the constructor, before setUp(), so that its constructor does not see the
* nonfunctional Looper.
*/
private MediaRecorder mMediaRecorder = new MediaRecorder();
public MediaRecorderTest() {
super("com.android.cts.stub", MediaStubActivity.class);
OUTPUT_PATH = new File(Environment.getExternalStorageDirectory(),
"record.out").getAbsolutePath();
OUTPUT_PATH2 = new File(Environment.getExternalStorageDirectory(),
"record2.out").getAbsolutePath();
}
@Override
protected void setUp() throws Exception {
mOutFile = new File(OUTPUT_PATH);
mOutFile2 = new File(OUTPUT_PATH2);
mMediaRecorder.reset();
mMediaRecorder.setOutputFile(OUTPUT_PATH);
mMediaRecorder.setOnInfoListener(new OnInfoListener() {
public void onInfo(MediaRecorder mr, int what, int extra) {
mOnInfoCalled = true;
}
});
mMediaRecorder.setOnErrorListener(new OnErrorListener() {
public void onError(MediaRecorder mr, int what, int extra) {
mOnErrorCalled = true;
}
});
super.setUp();
}
@Override
protected void tearDown() throws Exception {
mMediaRecorder.release();
if (mOutFile != null && mOutFile.exists()) {
mOutFile.delete();
}
if (mOutFile2 != null && mOutFile2.exists()) {
mOutFile2.delete();
}
if (mCamera != null) {
mCamera.release();
mCamera = null;
}
super.tearDown();
}
public void testRecorderCamera() throws Exception {
if (!hasCamera()) {
return;
}
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT);
mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE_IN_BPS);
mMediaRecorder.setPreviewDisplay(getActivity().getSurfaceHolder().getSurface());
mMediaRecorder.prepare();
mMediaRecorder.start();
Thread.sleep(1000);
mMediaRecorder.stop();
checkOutputExist();
}
@UiThreadTest
public void testSetCamera() throws Exception {
recordVideoUsingCamera(false);
}
public void testRecorderTimelapsedVideo() throws Exception {
recordVideoUsingCamera(true);
}
public void recordVideoUsingCamera(boolean timelapse) throws Exception {
int nCamera = Camera.getNumberOfCameras();
int durMs = timelapse? 4000: 1000;
for (int cameraId = 0; cameraId < nCamera; cameraId++) {
mCamera = Camera.open(cameraId);
recordVideoUsingCamera(mCamera, OUTPUT_PATH, durMs, timelapse);
mCamera.release();
mCamera = null;
assertTrue(checkLocationInFile(OUTPUT_PATH));
}
}
private void recordVideoUsingCamera(
Camera camera, String fileName, int durMs, boolean timelapse) throws Exception {
// FIXME:
// We should add some test case to use Camera.Parameters.getPreviewFpsRange()
// to get the supported video frame rate range.
Camera.Parameters params = camera.getParameters();
int frameRate = params.getPreviewFrameRate();
camera.unlock();
mMediaRecorder.setCamera(camera);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
mMediaRecorder.setVideoFrameRate(frameRate);
mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT);
mMediaRecorder.setPreviewDisplay(getActivity().getSurfaceHolder().getSurface());
mMediaRecorder.setOutputFile(fileName);
mMediaRecorder.setLocation(LATITUDE, LONGITUDE);
final double captureRate = VIDEO_TIMELAPSE_CAPTURE_RATE_FPS;
if (timelapse) {
mMediaRecorder.setCaptureRate(captureRate);
}
mMediaRecorder.prepare();
mMediaRecorder.start();
Thread.sleep(durMs);
mMediaRecorder.stop();
assertTrue(mOutFile.exists());
int targetDurMs = timelapse? ((int) (durMs * (captureRate / frameRate))): durMs;
boolean hasVideo = true;
boolean hasAudio = timelapse? false: true;
checkTracksAndDuration(targetDurMs, hasVideo, hasAudio, fileName);
}
private void checkTracksAndDuration(
int targetMs, boolean hasVideo, boolean hasAudio, String fileName) throws Exception {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(fileName);
String hasVideoStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
String hasAudioStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO);
assertTrue(hasVideo? hasVideoStr != null : hasVideoStr == null);
assertTrue(hasAudio? hasAudioStr != null : hasAudioStr == null);
// FIXME:
// If we could use fixed frame rate for video recording, we could also do more accurate
// check on the duration.
String durStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
assertTrue(durStr != null);
assertTrue(Integer.parseInt(durStr) > 0);
retriever.release();
retriever = null;
}
private boolean checkLocationInFile(String fileName) {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(fileName);
String location = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION);
if (location == null) {
Log.v(TAG, "No location information found in file " + fileName);
return false;
}
// parsing String location and recover the location inforamtion in floats
// Make sure the tolerance is very small - due to rounding errors?.
Log.v(TAG, "location: " + location);
// Get the position of the -/+ sign in location String, which indicates
// the beginning of the longtitude.
int index = location.lastIndexOf('-');
if (index == -1) {
index = location.lastIndexOf('+');
}
assertTrue("+ or - is not found", index != -1);
assertTrue("+ or - is only found at the beginning", index != 0);
float latitude = Float.parseFloat(location.substring(0, index - 1));
float longitude = Float.parseFloat(location.substring(index));
assertTrue("Incorrect latitude: " + latitude, Math.abs(latitude - LATITUDE) <= TOLERANCE);
assertTrue("Incorrect longitude: " + longitude, Math.abs(longitude - LONGITUDE) <= TOLERANCE);
return true;
}
private void checkOutputExist() {
assertTrue(mOutFile.exists());
assertTrue(mOutFile.length() > 0);
assertTrue(mOutFile.delete());
}
public void testRecorderVideo() throws Exception {
if (!hasCamera()) {
return;
}
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
mMediaRecorder.setOutputFile(OUTPUT_PATH2);
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
mMediaRecorder.setPreviewDisplay(getActivity().getSurfaceHolder().getSurface());
mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT);
FileOutputStream fos = new FileOutputStream(OUTPUT_PATH2);
FileDescriptor fd = fos.getFD();
mMediaRecorder.setOutputFile(fd);
long maxFileSize = MAX_FILE_SIZE * 10;
recordMedia(maxFileSize, mOutFile2);
assertFalse(checkLocationInFile(OUTPUT_PATH2));
}
public void testRecordingAudioInRawFormats() throws Exception {
testRecordAudioInRawFormat(
MediaRecorder.OutputFormat.AMR_NB,
MediaRecorder.AudioEncoder.AMR_NB);
testRecordAudioInRawFormat(
MediaRecorder.OutputFormat.AMR_WB,
MediaRecorder.AudioEncoder.AMR_WB);
testRecordAudioInRawFormat(
MediaRecorder.OutputFormat.AAC_ADTS,
MediaRecorder.AudioEncoder.AAC);
}
private void testRecordAudioInRawFormat(
int fileFormat, int codec) throws Exception {
if (!hasMicrophone()) {
return;
}
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mMediaRecorder.setOutputFormat(fileFormat);
mMediaRecorder.setOutputFile(OUTPUT_PATH);
mMediaRecorder.setAudioEncoder(codec);
recordMedia(MAX_FILE_SIZE, mOutFile);
}
public void testGetAudioSourceMax() throws Exception {
final int max = MediaRecorder.getAudioSourceMax();
assertTrue(MediaRecorder.AudioSource.DEFAULT <= max);
assertTrue(MediaRecorder.AudioSource.MIC <= max);
assertTrue(MediaRecorder.AudioSource.CAMCORDER <= max);
assertTrue(MediaRecorder.AudioSource.VOICE_CALL <= max);
assertTrue(MediaRecorder.AudioSource.VOICE_COMMUNICATION <= max);
assertTrue(MediaRecorder.AudioSource.VOICE_DOWNLINK <= max);
assertTrue(MediaRecorder.AudioSource.VOICE_RECOGNITION <= max);
assertTrue(MediaRecorder.AudioSource.VOICE_UPLINK <= max);
}
public void testRecorderAudio() throws Exception {
if (!hasMicrophone()) {
return;
}
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
assertEquals(0, mMediaRecorder.getMaxAmplitude());
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mMediaRecorder.setOutputFile(OUTPUT_PATH);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mMediaRecorder.setAudioChannels(AUDIO_NUM_CHANNELS);
mMediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE_HZ);
mMediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE_IN_BPS);
recordMedia(MAX_FILE_SIZE, mOutFile);
}
public void testOnInfoListener() throws Exception {
if (!hasMicrophone()) {
return;
}
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mMediaRecorder.setMaxDuration(MAX_DURATION_MSEC);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mMediaRecorder.prepare();
mMediaRecorder.start();
Thread.sleep(RECORD_TIME);
assertTrue(mOnInfoCalled);
}
public void testSetMaxDuration() throws Exception {
if (!hasMicrophone()) {
return;
}
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mMediaRecorder.setMaxDuration(0);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mMediaRecorder.prepare();
mMediaRecorder.start();
Thread.sleep(RECORD_TIME * 30);
mMediaRecorder.stop();
checkOutputExist();
}
public void testSetMaxFileSize() throws Exception {
if (!hasMicrophone()) {
return;
}
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mMediaRecorder.setMaxFileSize(0);
mMediaRecorder.prepare();
mMediaRecorder.start();
Thread.sleep(RECORD_TIME * 30);
mMediaRecorder.stop();
checkOutputExist();
}
public void testOnErrorListener() throws Exception {
if (!hasMicrophone()) {
return;
}
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
recordMedia(MAX_FILE_SIZE, mOutFile);
// TODO: how can we trigger a recording error?
assertFalse(mOnErrorCalled);
}
private void recordMedia(long maxFileSize, File outFile) throws Exception {
mMediaRecorder.setMaxFileSize(maxFileSize);
mMediaRecorder.prepare();
mMediaRecorder.start();
Thread.sleep(RECORD_TIME);
mMediaRecorder.stop();
// onErrorListner sometimes fails in the 3rd assert.
// Fix trial: give some time to write, but this may not fix it.
Thread.sleep(1000);
assertTrue(outFile.exists());
// The max file size is always guaranteed.
// We just make sure that the margin is not too big
assertTrue(outFile.length() < 1.1 * maxFileSize);
assertTrue(outFile.length() > 0);
}
private boolean hasCamera() {
return getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
}
private boolean hasMicrophone() {
return getActivity().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_MICROPHONE);
}
}