transcoding: Rework MediaTranscodeManager test.

1. Remove MediaTranscodeManagerWithMockServiceTest and move
   all the tests to MediaTranscodeManagerTest.
2. Move the service died test in MediaTranscodeManagerTest into a
   seperate ServiceDied test as it affect the normal test when
   killing the service.

Bug: 160260102
Test: Unit test
Change-Id: I192200744a44d4d724289f6001255100b332c968
This commit is contained in:
hkuang
2020-08-30 13:59:52 -07:00
committed by Hangyu Kuang
parent 687e10a45e
commit 48be9935c0
5 changed files with 484 additions and 573 deletions

View File

@@ -31,6 +31,9 @@ do
adb push --sync $file /data/user/0/com.android.mediatranscodingtest/cache/
done
echo "[==========] running real transcoding tests"
echo "[==========] running MediaTranscodeManagerTest"
adb shell am instrument -e class com.android.mediatranscodingtest.MediaTranscodeManagerTest -w com.android.mediatranscodingtest/.MediaTranscodingTestRunner
echo "[==========] running MediaTranscodeManagerDiedTest"
adb shell am instrument -e class com.android.mediatranscodingtest.MediaTranscodeManagerDiedTest -w com.android.mediatranscodingtest/.MediaTranscodingTestRunner

View File

@@ -0,0 +1,255 @@
/*
* Copyright (C) 2020 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.mediatranscodingtest;
import android.content.ContentResolver;
import android.content.Context;
import android.media.MediaFormat;
import android.media.MediaTranscodeManager;
import android.media.MediaTranscodeManager.TranscodingJob;
import android.media.MediaTranscodeManager.TranscodingRequest;
import android.net.Uri;
import android.os.Bundle;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.uiautomator.UiDevice;
import org.junit.Test;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/*
* Service died tests for MediaTranscodeManager in the media framework.
*
* To run this test suite:
make frameworks/base/media/tests/MediaTranscodingTest
make mediatranscodingtest
adb install -r testcases/mediatranscodingtest/arm64/mediatranscodingtest.apk
adb shell am instrument -e class \
com.android.mediatranscodingtest.MediaTranscodeManagerDiedTest \
-w com.android.mediatranscodingtest/.MediaTranscodingTestRunner
*
*/
public class MediaTranscodeManagerDiedTest
extends ActivityInstrumentationTestCase2<MediaTranscodingTest> {
private static final String TAG = "MediaTranscodeManagerDiedTest";
/** The time to wait for the transcode operation to complete before failing the test. */
private static final int TRANSCODE_TIMEOUT_SECONDS = 10;
/** Maximum number of retry to connect to the service. */
private static final int CONNECT_SERVICE_RETRY_COUNT = 100;
/** Interval between trying to reconnect to the service. */
private static final int INTERVAL_CONNECT_SERVICE_RETRY_MS = 40;
private Context mContext;
private MediaTranscodeManager mMediaTranscodeManager = null;
private Uri mSourceHEVCVideoUri = null;
private Uri mSourceAVCVideoUri = null;
private Uri mDestinationUri = null;
// Setting for transcoding to H.264.
private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
private static final int BIT_RATE = 20000000; // 20Mbps
private static final int WIDTH = 1920;
private static final int HEIGHT = 1080;
public MediaTranscodeManagerDiedTest() {
super("com.android.MediaTranscodeManagerTest", MediaTranscodingTest.class);
}
// Copy the resource to cache.
private Uri resourceToUri(Context context, int resId, String name) throws IOException {
Uri resUri = new Uri.Builder()
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(context.getResources().getResourcePackageName(resId))
.appendPath(context.getResources().getResourceTypeName(resId))
.appendPath(context.getResources().getResourceEntryName(resId))
.build();
Uri cacheUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+ mContext.getCacheDir().getAbsolutePath() + "/" + name);
InputStream is = mContext.getResources().openRawResource(resId);
OutputStream os = mContext.getContentResolver().openOutputStream(cacheUri);
FileUtils.copy(is, os);
return cacheUri;
}
private static Uri generateNewUri(Context context, String filename) {
File outFile = new File(context.getExternalCacheDir(), filename);
return Uri.fromFile(outFile);
}
/**
* Creates a MediaFormat with the basic set of values.
*/
private static MediaFormat createMediaFormat() {
MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT);
format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
return format;
}
private MediaTranscodeManager getManager() {
for (int count = 1; count <= CONNECT_SERVICE_RETRY_COUNT; count++) {
Log.d(TAG, "Trying to connect to service. Try count: " + count);
MediaTranscodeManager manager = mContext.getSystemService(MediaTranscodeManager.class);
if (manager != null) {
return manager;
}
try {
// Sleep a bit before retry.
Thread.sleep(INTERVAL_CONNECT_SERVICE_RETRY_MS);
} catch (InterruptedException ie) {
/* ignore */
}
}
throw new UnsupportedOperationException("Failed to acquire MediaTranscodeManager");
}
@Override
public void setUp() throws Exception {
Log.d(TAG, "setUp");
super.setUp();
mContext = getInstrumentation().getContext();
mMediaTranscodeManager = getManager();
assertNotNull(mMediaTranscodeManager);
androidx.test.InstrumentationRegistry.registerInstance(getInstrumentation(), new Bundle());
// Setup source HEVC file uri.
mSourceHEVCVideoUri = resourceToUri(mContext, R.raw.VideoOnlyHEVC, "VideoOnlyHEVC.mp4");
// Setup source AVC file uri.
mSourceAVCVideoUri = resourceToUri(mContext, R.raw.VideoOnlyAVC,
"VideoOnlyAVC.mp4");
// Setup destination file.
mDestinationUri = generateNewUri(mContext, "transcoded.mp4");
}
@Override
public void tearDown() throws Exception {
super.tearDown();
}
// [[ $(adb shell whoami) == "root" ]]
private boolean checkIfRoot() {
try (ParcelFileDescriptor result = getInstrumentation().getUiAutomation()
.executeShellCommand("whoami");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
new FileInputStream(result.getFileDescriptor())))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
if (line.contains("root")) {
return true;
}
}
} catch (IOException ie) {
return false;
}
return false;
}
private String executeShellCommand(String cmd) throws Exception {
return UiDevice.getInstance(
InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
}
@Test
public void testHandleTranscoderServiceDied() throws Exception {
if (!checkIfRoot()) {
throw new AssertionError("must be root to run this test; try adb root?");
}
Semaphore transcodeCompleteSemaphore = new Semaphore(0);
Semaphore jobStartedSemaphore = new Semaphore(0);
// Transcode a 15 seconds video, so that the transcoding is not finished when we kill the
// service.
Uri srcUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+ mContext.getCacheDir().getAbsolutePath() + "/longtest_15s.mp4");
Uri destinationUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+ mContext.getCacheDir().getAbsolutePath() + "/HevcTranscode.mp4");
TranscodingRequest request =
new TranscodingRequest.Builder()
.setSourceUri(mSourceHEVCVideoUri)
.setDestinationUri(destinationUri)
.setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
.setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
.setVideoTrackFormat(createMediaFormat())
.build();
Executor listenerExecutor = Executors.newSingleThreadExecutor();
Log.i(TAG, "transcoding to " + createMediaFormat());
TranscodingJob job = mMediaTranscodeManager.enqueueRequest(request, listenerExecutor,
transcodingJob -> {
Log.d(TAG, "Transcoding completed with result: " + transcodingJob.getResult());
transcodeCompleteSemaphore.release();
});
assertNotNull(job);
AtomicInteger progressUpdateCount = new AtomicInteger(0);
// Set progress update executor and use the same executor as result listener.
job.setOnProgressUpdateListener(listenerExecutor,
new TranscodingJob.OnProgressUpdateListener() {
@Override
public void onProgressUpdate(int newProgress) {
if (newProgress > 0) {
jobStartedSemaphore.release();
}
}
});
// Wait for progress update so the job is in running state.
jobStartedSemaphore.tryAcquire(TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
assertTrue("Job is not running", job.getStatus() == TranscodingJob.STATUS_RUNNING);
// Kills the service and expects receiving failure of the job.
executeShellCommand("pkill -f media.transcoding");
Log.d(TAG, "testMediaTranscodeManager - Waiting for transcode result.");
boolean finishedOnTime = transcodeCompleteSemaphore.tryAcquire(
TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
assertTrue("Invalid job status", job.getStatus() == TranscodingJob.STATUS_FINISHED);
assertTrue("Invalid job result", job.getResult()== TranscodingJob.RESULT_ERROR);
}
}

View File

@@ -16,12 +16,15 @@
package com.android.mediatranscodingtest;
import static org.testng.Assert.assertThrows;
import android.content.ContentResolver;
import android.content.Context;
import android.media.MediaFormat;
import android.media.MediaTranscodeManager;
import android.media.MediaTranscodeManager.TranscodingJob;
import android.media.MediaTranscodeManager.TranscodingRequest;
import android.media.TranscodingTestConfig;
import android.net.Uri;
import android.os.Bundle;
import android.os.FileUtils;
@@ -180,6 +183,227 @@ public class MediaTranscodeManagerTest
super.tearDown();
}
/**
* Verify that setting null destination uri will throw exception.
*/
@Test
public void testCreateTranscodingRequestWithNullDestinationUri() throws Exception {
assertThrows(IllegalArgumentException.class, () -> {
TranscodingRequest request =
new TranscodingRequest.Builder()
.setSourceUri(mSourceHEVCVideoUri)
.setDestinationUri(null)
.setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
.setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
.setVideoTrackFormat(createMediaFormat())
.build();
});
}
/**
* Verify that setting null source uri will throw exception.
*/
@Test
public void testCreateTranscodingRequestWithNullSourceUri() throws Exception {
assertThrows(IllegalArgumentException.class, () -> {
TranscodingRequest request =
new TranscodingRequest.Builder()
.setSourceUri(null)
.setDestinationUri(mDestinationUri)
.setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
.setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
.build();
});
}
/**
* Verify that not setting source uri will throw exception.
*/
@Test
public void testCreateTranscodingRequestWithoutSourceUri() throws Exception {
assertThrows(UnsupportedOperationException.class, () -> {
TranscodingRequest request =
new TranscodingRequest.Builder()
.setDestinationUri(mDestinationUri)
.setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
.setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
.setVideoTrackFormat(createMediaFormat())
.build();
});
}
/**
* Verify that not setting destination uri will throw exception.
*/
@Test
public void testCreateTranscodingRequestWithoutDestinationUri() throws Exception {
assertThrows(UnsupportedOperationException.class, () -> {
TranscodingRequest request =
new TranscodingRequest.Builder()
.setSourceUri(mSourceHEVCVideoUri)
.setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
.setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
.setVideoTrackFormat(createMediaFormat())
.build();
});
}
/**
* Verify that setting image transcoding mode will throw exception.
*/
@Test
public void testCreateTranscodingRequestWithUnsupportedMode() throws Exception {
assertThrows(UnsupportedOperationException.class, () -> {
TranscodingRequest request =
new TranscodingRequest.Builder()
.setSourceUri(mSourceHEVCVideoUri)
.setDestinationUri(mDestinationUri)
.setType(MediaTranscodeManager.TRANSCODING_TYPE_IMAGE)
.setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
.setVideoTrackFormat(createMediaFormat())
.build();
});
}
/**
* Verify that setting video transcoding without setting video format will throw exception.
*/
@Test
public void testCreateTranscodingRequestWithoutVideoFormat() throws Exception {
assertThrows(UnsupportedOperationException.class, () -> {
TranscodingRequest request =
new TranscodingRequest.Builder()
.setSourceUri(mSourceHEVCVideoUri)
.setDestinationUri(mDestinationUri)
.setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
.setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
.build();
});
}
void testTranscodingWithExpectResult(Uri srcUri, Uri dstUri, int expectedResult)
throws Exception {
Semaphore transcodeCompleteSemaphore = new Semaphore(0);
TranscodingTestConfig testConfig = new TranscodingTestConfig();
testConfig.passThroughMode = true;
testConfig.processingTotalTimeMs = 300; // minimum time spent on transcoding.
TranscodingRequest request =
new TranscodingRequest.Builder()
.setSourceUri(srcUri)
.setDestinationUri(dstUri)
.setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
.setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
.setVideoTrackFormat(createMediaFormat())
.setTestConfig(testConfig)
.build();
Executor listenerExecutor = Executors.newSingleThreadExecutor();
TranscodingJob job = mMediaTranscodeManager.enqueueRequest(request, listenerExecutor,
transcodingJob -> {
Log.d(TAG, "Transcoding completed with result: " + transcodingJob.getResult());
assertTrue("Transcoding should failed.",
transcodingJob.getResult() == expectedResult);
transcodeCompleteSemaphore.release();
});
assertNotNull(job);
if (job != null) {
Log.d(TAG, "testMediaTranscodeManager - Waiting for transcode to complete.");
boolean finishedOnTime = transcodeCompleteSemaphore.tryAcquire(
TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
assertTrue("Transcode failed to complete in time.", finishedOnTime);
}
if (expectedResult == TranscodingJob.RESULT_SUCCESS) {
// Checks the destination file get generated.
File file = new File(dstUri.getPath());
assertTrue("Failed to create destination file", file.exists());
// Removes the file.
file.delete();
}
}
// Tests transcoding from invalid a invalid and expects failure.
@Test
public void testTranscodingInvalidSrcUri() throws Exception {
Log.d(TAG, "Starting: testMediaTranscodeManager");
Uri invalidSrcUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
+ mContext.getPackageName() + "/source.mp4");
Log.d(TAG, "Transcoding from source: " + invalidSrcUri);
// Create a file Uri: file:///data/user/0/com.android.mediatranscodingtest/cache/temp.mp4
// The full path of this file is:
// /data/user/0/com.android.mediatranscodingtest/cache/temp.mp4
Uri destinationUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
+ mContext.getPackageName() + "/temp.mp4");
Log.d(TAG, "Transcoding to destination: " + destinationUri);
testTranscodingWithExpectResult(invalidSrcUri, destinationUri, TranscodingJob.RESULT_ERROR);
}
// Tests transcoding to a uri in res folder and expects failure as we could not write to res
// folder.
@Test
public void testTranscodingToResFolder() throws Exception {
Log.d(TAG, "Starting: testMediaTranscodeManager");
// Create a file Uri: file:///data/user/0/com.android.mediatranscodingtest/cache/temp.mp4
// The full path of this file is:
// /data/user/0/com.android.mediatranscodingtest/cache/temp.mp4
Uri destinationUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
+ mContext.getPackageName() + "/temp.mp4");
Log.d(TAG, "Transcoding to destination: " + destinationUri);
testTranscodingWithExpectResult(mSourceHEVCVideoUri, destinationUri,
TranscodingJob.RESULT_ERROR);
}
// Tests transcoding to a uri in internal storage folder and expects success.
@Test
public void testTranscodingToCacheDir() throws Exception {
Log.d(TAG, "Starting: testMediaTranscodeManager");
// Create a file Uri: file:///data/user/0/com.android.mediatranscodingtest/cache/temp.mp4
// The full path of this file is:
// /data/user/0/com.android.mediatranscodingtest/cache/temp.mp4
Uri destinationUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+ mContext.getCacheDir().getAbsolutePath() + "/temp.mp4");
testTranscodingWithExpectResult(mSourceHEVCVideoUri, destinationUri,
TranscodingJob.RESULT_SUCCESS);
}
// Tests transcoding to a uri in internal files directory and expects success.
@Test
public void testTranscodingToInternalFilesDir() throws Exception {
Log.d(TAG, "Starting: testMediaTranscodeManager");
// Create a file Uri:
// file:///storage/emulated/0/Android/data/com.android.mediatranscodingtest/files/temp.mp4
Uri destinationUri = Uri.fromFile(new File(mContext.getFilesDir(), "temp.mp4"));
Log.i(TAG, "Transcoding to files dir: " + destinationUri);
testTranscodingWithExpectResult(mSourceHEVCVideoUri, destinationUri,
TranscodingJob.RESULT_SUCCESS);
}
// Tests transcoding to a uri in external files directory and expects success.
@Test
public void testTranscodingToExternalFilesDir() throws Exception {
Log.d(TAG, "Starting: testMediaTranscodeManager");
// Create a file Uri: file:///data/user/0/com.android.mediatranscodingtest/files/temp.mp4
Uri destinationUri = Uri.fromFile(new File(mContext.getExternalFilesDir(null), "temp.mp4"));
Log.i(TAG, "Transcoding to files dir: " + destinationUri);
testTranscodingWithExpectResult(mSourceHEVCVideoUri, destinationUri,
TranscodingJob.RESULT_SUCCESS);
}
@Test
public void testTranscodingFromHevcToAvc() throws Exception {
Semaphore transcodeCompleteSemaphore = new Semaphore(0);
@@ -354,73 +578,5 @@ public class MediaTranscodeManagerTest
return UiDevice.getInstance(
InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
}
@Test
public void testHandleTranscoderServiceDied() throws Exception {
try {
if (!checkIfRoot()) {
throw new AssertionError("must be root to run this test; try adb root?");
} else {
Log.i(TAG, "Device is root");
}
} catch (IOException e) {
throw new AssertionError(e);
}
Semaphore transcodeCompleteSemaphore = new Semaphore(0);
Semaphore jobStartedSemaphore = new Semaphore(0);
// Transcode a 15 seconds video, so that the transcoding is not finished when we kill the
// service.
Uri srcUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+ mContext.getCacheDir().getAbsolutePath() + "/longtest_15s.mp4");
Uri destinationUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+ mContext.getCacheDir().getAbsolutePath() + "/HevcTranscode.mp4");
TranscodingRequest request =
new TranscodingRequest.Builder()
.setSourceUri(mSourceHEVCVideoUri)
.setDestinationUri(destinationUri)
.setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
.setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
.setVideoTrackFormat(createMediaFormat())
.build();
Executor listenerExecutor = Executors.newSingleThreadExecutor();
Log.i(TAG, "transcoding to " + createMediaFormat());
TranscodingJob job = mMediaTranscodeManager.enqueueRequest(request, listenerExecutor,
transcodingJob -> {
Log.d(TAG, "Transcoding completed with result: " + transcodingJob.getResult());
assertEquals(transcodingJob.getResult(), TranscodingJob.RESULT_ERROR);
transcodeCompleteSemaphore.release();
});
assertNotNull(job);
AtomicInteger progressUpdateCount = new AtomicInteger(0);
// Set progress update executor and use the same executor as result listener.
job.setOnProgressUpdateListener(listenerExecutor,
new TranscodingJob.OnProgressUpdateListener() {
@Override
public void onProgressUpdate(int newProgress) {
if (newProgress > 0) {
jobStartedSemaphore.release();
}
}
});
// Wait for progress update so the job is in running state.
jobStartedSemaphore.tryAcquire(TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
assertTrue("Job is not running", job.getStatus() == TranscodingJob.STATUS_RUNNING);
// Kills the service and expects receiving failure of the job.
executeShellCommand("pkill -f media.transcoding");
Log.d(TAG, "testMediaTranscodeManager - Waiting for transcode result.");
boolean finishedOnTime = transcodeCompleteSemaphore.tryAcquire(
TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
assertTrue("Invalid job status", job.getStatus() == TranscodingJob.STATUS_FINISHED);
}
}

View File

@@ -1,503 +0,0 @@
/*
* Copyright (C) 2019 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.mediatranscodingtest;
import static org.testng.Assert.assertThrows;
import android.content.ContentResolver;
import android.content.Context;
import android.media.IMediaTranscodingService;
import android.media.ITranscodingClient;
import android.media.ITranscodingClientCallback;
import android.media.MediaFormat;
import android.media.MediaTranscodeManager;
import android.media.MediaTranscodeManager.TranscodingJob;
import android.media.MediaTranscodeManager.TranscodingRequest;
import android.media.TranscodingJobParcel;
import android.media.TranscodingRequestParcel;
import android.media.TranscodingResultParcel;
import android.media.TranscodingTestConfig;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import org.junit.Test;
import java.io.File;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/*
* Functional tests for MediaTranscodeManager in the media framework.
* The test uses a mock Transcoding service as backend to test the API functionality.
*
* To run this test suite:
make frameworks/base/media/tests/MediaTranscodingTest
make mediatranscodingtest
adb install -r testcases/mediatranscodingtest/arm64/mediatranscodingtest.apk
adb shell am instrument -e class \
com.android.mediatranscodingtest.MediaTranscodeManagerWithMockServiceTest \
-w com.android.mediatranscodingtest/.MediaTranscodingTestRunner
*
*/
public class MediaTranscodeManagerWithMockServiceTest
extends ActivityInstrumentationTestCase2<MediaTranscodingTest> {
private static final String TAG = "MediaTranscodeManagerWithMockServiceTest";
/** The time to wait for the transcode operation to complete before failing the test. */
private static final int TRANSCODE_TIMEOUT_SECONDS = 2;
private Context mContext;
private MediaTranscodeManager mMediaTranscodeManager = null;
private Uri mSourceHEVCVideoUri = null;
private Uri mDestinationUri = null;
// Use mock transcoding service for testing the api.
private MockTranscodingService mTranscodingService = null;
// Setting for transcoding to H.264.
private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
private static final int BIT_RATE = 2000000; // 2Mbps
private static final int WIDTH = 1920;
private static final int HEIGHT = 1080;
// A mock transcoding service that will take constant 300ms to process each transcoding job.
// Instead of doing real transcoding, it will return the dst uri directly.
class MockTranscodingService extends IMediaTranscodingService.Stub {
private final ScheduledExecutorService mJobScheduler = Executors.newScheduledThreadPool(1);
private int mNumOfClients = 0;
private AtomicInteger mJobId = new AtomicInteger();
// A runnable that will process the job.
private class ProcessingJobRunnable implements Runnable {
private TranscodingJobParcel mJob;
private ITranscodingClientCallback mCallback;
private ConcurrentMap<Integer, ScheduledFuture<?>> mJobMap;
ProcessingJobRunnable(ITranscodingClientCallback callback,
TranscodingJobParcel job,
ConcurrentMap<Integer, ScheduledFuture<?>> jobMap) {
mJob = job;
mCallback = callback;
mJobMap = jobMap;
}
@Override
public void run() {
Log.d(TAG, "Start to process job " + mJob.jobId);
TranscodingResultParcel result = new TranscodingResultParcel();
try {
// Try to open the source fd.
ParcelFileDescriptor sourceFd = mCallback.openFileDescriptor(
mJob.request.sourceFilePath, "r");
if (sourceFd == null) {
Log.d(TAG, "Failed to open sourceFd");
// TODO(hkuang): Pass the correct error code.
mCallback.onTranscodingFailed(mJob.jobId, 1);
return;
} else {
Log.d(TAG, "Successfully open sourceFd");
}
// Try to write to the destination fd.
ParcelFileDescriptor destinationFd = mCallback.openFileDescriptor(
mJob.request.destinationFilePath, "w");
if (destinationFd == null) {
Log.d(TAG, "Failed to open destinationFd");
// TODO(hkuang): Pass the correct error code.
mCallback.onTranscodingFailed(mJob.jobId, 1);
return;
} else {
Log.d(TAG, "Successfully open destinationFd");
}
mCallback.onTranscodingFinished(mJob.jobId, result);
// Removes the job from job map.
mJobMap.remove(mJob.jobId);
} catch (RemoteException re) {
Log.e(TAG, "Failed to callback to client");
}
}
}
@Override
public ITranscodingClient registerClient(ITranscodingClientCallback callback,
String clientName, String opPackageName, int clientUid, int clientPid)
throws RemoteException {
Log.d(TAG, "MockTranscodingService creates one client");
ITranscodingClient client = new ITranscodingClient.Stub() {
private final ConcurrentMap<Integer, ScheduledFuture<?>> mPendingTranscodingJobs =
new ConcurrentHashMap<Integer, ScheduledFuture<?>>();
@Override
public boolean submitRequest(TranscodingRequestParcel inRequest,
TranscodingJobParcel outjob) {
Log.d(TAG, "Mock client gets submitRequest");
try {
outjob.request = inRequest;
outjob.jobId = mJobId.getAndIncrement();
Log.i(TAG, "Generate new job " + outjob.jobId);
Log.i(TAG, "Source Uri " + inRequest.sourceFilePath);
Log.i(TAG, "Destination Uri " + inRequest.destinationFilePath);
// Schedules the job to run after inRequest.processingDelayMs.
ScheduledFuture<?> transcodingFuture = mJobScheduler.schedule(
new ProcessingJobRunnable(callback, outjob,
mPendingTranscodingJobs),
inRequest.testConfig == null ? 0
: inRequest.testConfig.processingTotalTimeMs,
TimeUnit.MILLISECONDS);
mPendingTranscodingJobs.put(outjob.jobId, transcodingFuture);
} catch (RejectedExecutionException e) {
Log.e(TAG, "Failed to schedule transcoding job: " + e);
return false;
}
return true;
}
@Override
public boolean cancelJob(int jobId) throws RemoteException {
Log.d(TAG, "Mock client gets cancelJob " + jobId);
// Cancels the job is still in the mPendingTranscodingJobs.
if (mPendingTranscodingJobs.containsKey(jobId)) {
// Cancel the future task for transcoding.
mPendingTranscodingJobs.get(jobId).cancel(true);
// Remove the job from the mPendingTranscodingJobs.
mPendingTranscodingJobs.remove(jobId);
return true;
}
return false;
}
@Override
public boolean getJobWithId(int jobId, TranscodingJobParcel job)
throws RemoteException {
// This will be implemented this if needed in the test.
return true;
}
@Override
public void unregister() throws RemoteException {
Log.d(TAG, "Mock client gets unregister");
// This will be implemented this if needed in the test.
mNumOfClients--;
}
};
mNumOfClients++;
return client;
}
@Override
public int getNumOfClients() throws RemoteException {
return mNumOfClients;
}
}
public MediaTranscodeManagerWithMockServiceTest() {
super("com.android.MediaTranscodeManagerWithMockServiceTest", MediaTranscodingTest.class);
}
private static Uri resourceToUri(Context context, int resId) {
Uri uri = new Uri.Builder()
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(context.getResources().getResourcePackageName(resId))
.appendPath(context.getResources().getResourceTypeName(resId))
.appendPath(context.getResources().getResourceEntryName(resId))
.build();
return uri;
}
private static Uri generateNewUri(Context context, String filename) {
File outFile = new File(context.getExternalCacheDir(), filename);
return Uri.fromFile(outFile);
}
// Generates a invalid uri which will let the mock service return transcoding failure.
private static Uri generateInvalidTranscodingUri(Context context) {
File outFile = new File(context.getExternalCacheDir(), "InvalidUri.mp4");
return Uri.fromFile(outFile);
}
/**
* Creates a MediaFormat with the basic set of values.
*/
private static MediaFormat createMediaFormat() {
MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT);
format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
return format;
}
@Override
public void setUp() throws Exception {
Log.d(TAG, "setUp");
super.setUp();
mTranscodingService = new MockTranscodingService();
mContext = getInstrumentation().getContext();
mMediaTranscodeManager = mContext.getSystemService(MediaTranscodeManager.class);
assertNotNull(mMediaTranscodeManager);
// Setup source HEVC file uri.
mSourceHEVCVideoUri = resourceToUri(mContext, R.raw.VideoOnlyHEVC);
// Setup destination file.
mDestinationUri = generateNewUri(mContext, "transcoded.mp4");
}
@Override
public void tearDown() throws Exception {
super.tearDown();
}
/**
* Verify that setting null destination uri will throw exception.
*/
@Test
public void testCreateTranscodingRequestWithNullDestinationUri() throws Exception {
assertThrows(IllegalArgumentException.class, () -> {
TranscodingRequest request =
new TranscodingRequest.Builder()
.setSourceUri(mSourceHEVCVideoUri)
.setDestinationUri(null)
.setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
.setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
.setVideoTrackFormat(createMediaFormat())
.build();
});
}
/**
* Verify that setting null source uri will throw exception.
*/
@Test
public void testCreateTranscodingRequestWithNullSourceUri() throws Exception {
assertThrows(IllegalArgumentException.class, () -> {
TranscodingRequest request =
new TranscodingRequest.Builder()
.setSourceUri(null)
.setDestinationUri(mDestinationUri)
.setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
.setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
.build();
});
}
/**
* Verify that not setting source uri will throw exception.
*/
@Test
public void testCreateTranscodingRequestWithoutSourceUri() throws Exception {
assertThrows(UnsupportedOperationException.class, () -> {
TranscodingRequest request =
new TranscodingRequest.Builder()
.setDestinationUri(mDestinationUri)
.setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
.setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
.setVideoTrackFormat(createMediaFormat())
.build();
});
}
/**
* Verify that not setting destination uri will throw exception.
*/
@Test
public void testCreateTranscodingRequestWithoutDestinationUri() throws Exception {
assertThrows(UnsupportedOperationException.class, () -> {
TranscodingRequest request =
new TranscodingRequest.Builder()
.setSourceUri(mSourceHEVCVideoUri)
.setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
.setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
.setVideoTrackFormat(createMediaFormat())
.build();
});
}
/**
* Verify that setting image transcoding mode will throw exception.
*/
@Test
public void testCreateTranscodingRequestWithUnsupportedMode() throws Exception {
assertThrows(UnsupportedOperationException.class, () -> {
TranscodingRequest request =
new TranscodingRequest.Builder()
.setSourceUri(mSourceHEVCVideoUri)
.setDestinationUri(mDestinationUri)
.setType(MediaTranscodeManager.TRANSCODING_TYPE_IMAGE)
.setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
.setVideoTrackFormat(createMediaFormat())
.build();
});
}
/**
* Verify that setting video transcoding without setting video format will throw exception.
*/
@Test
public void testCreateTranscodingRequestWithoutVideoFormat() throws Exception {
assertThrows(UnsupportedOperationException.class, () -> {
TranscodingRequest request =
new TranscodingRequest.Builder()
.setSourceUri(mSourceHEVCVideoUri)
.setDestinationUri(mDestinationUri)
.setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
.setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
.build();
});
}
void testTranscodingWithExpectResult(Uri srcUri, Uri dstUri, int expectedResult)
throws Exception {
Semaphore transcodeCompleteSemaphore = new Semaphore(0);
TranscodingTestConfig testConfig = new TranscodingTestConfig();
testConfig.passThroughMode = true;
testConfig.processingTotalTimeMs = 300; // minimum time spent on transcoding.
TranscodingRequest request =
new TranscodingRequest.Builder()
.setSourceUri(srcUri)
.setDestinationUri(dstUri)
.setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
.setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
.setVideoTrackFormat(createMediaFormat())
.setTestConfig(testConfig)
.build();
Executor listenerExecutor = Executors.newSingleThreadExecutor();
TranscodingJob job = mMediaTranscodeManager.enqueueRequest(request, listenerExecutor,
transcodingJob -> {
Log.d(TAG, "Transcoding completed with result: " + transcodingJob.getResult());
assertTrue("Transcoding should failed.",
transcodingJob.getResult() == expectedResult);
transcodeCompleteSemaphore.release();
});
assertNotNull(job);
if (job != null) {
Log.d(TAG, "testMediaTranscodeManager - Waiting for transcode to complete.");
boolean finishedOnTime = transcodeCompleteSemaphore.tryAcquire(
TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
assertTrue("Transcode failed to complete in time.", finishedOnTime);
}
if (expectedResult == TranscodingJob.RESULT_SUCCESS) {
// Checks the destination file get generated.
File file = new File(dstUri.getPath());
assertTrue("Failed to create destination file", file.exists());
// Removes the file.
file.delete();
}
}
// Tests transcoding from invalid a invalid and expects failure.
@Test
public void testTranscodingInvalidSrcUri() throws Exception {
Log.d(TAG, "Starting: testMediaTranscodeManager");
Uri invalidSrcUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
+ mContext.getPackageName() + "/source.mp4");
Log.d(TAG, "Transcoding from source: " + invalidSrcUri);
// Create a file Uri: file:///data/user/0/com.android.mediatranscodingtest/cache/temp.mp4
// The full path of this file is:
// /data/user/0/com.android.mediatranscodingtest/cache/temp.mp4
Uri destinationUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
+ mContext.getPackageName() + "/temp.mp4");
Log.d(TAG, "Transcoding to destination: " + destinationUri);
testTranscodingWithExpectResult(invalidSrcUri, destinationUri, TranscodingJob.RESULT_ERROR);
}
// Tests transcoding to a uri in res folder and expects failure as we could not write to res
// folder.
@Test
public void testTranscodingToResFolder() throws Exception {
Log.d(TAG, "Starting: testMediaTranscodeManager");
// Create a file Uri: file:///data/user/0/com.android.mediatranscodingtest/cache/temp.mp4
// The full path of this file is:
// /data/user/0/com.android.mediatranscodingtest/cache/temp.mp4
Uri destinationUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
+ mContext.getPackageName() + "/temp.mp4");
Log.d(TAG, "Transcoding to destination: " + destinationUri);
testTranscodingWithExpectResult(mSourceHEVCVideoUri, destinationUri,
TranscodingJob.RESULT_ERROR);
}
// Tests transcoding to a uri in internal storage folder and expects success.
@Test
public void testTranscodingToCacheDir() throws Exception {
Log.d(TAG, "Starting: testMediaTranscodeManager");
// Create a file Uri: file:///data/user/0/com.android.mediatranscodingtest/cache/temp.mp4
// The full path of this file is:
// /data/user/0/com.android.mediatranscodingtest/cache/temp.mp4
Uri destinationUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+ mContext.getCacheDir().getAbsolutePath() + "/temp.mp4");
testTranscodingWithExpectResult(mSourceHEVCVideoUri, destinationUri,
TranscodingJob.RESULT_SUCCESS);
}
// Tests transcoding to a uri in internal files directory and expects success.
@Test
public void testTranscodingToInternalFilesDir() throws Exception {
Log.d(TAG, "Starting: testMediaTranscodeManager");
// Create a file Uri:
// file:///storage/emulated/0/Android/data/com.android.mediatranscodingtest/files/temp.mp4
Uri destinationUri = Uri.fromFile(new File(mContext.getFilesDir(), "temp.mp4"));
Log.i(TAG, "Transcoding to files dir: " + destinationUri);
testTranscodingWithExpectResult(mSourceHEVCVideoUri, destinationUri,
TranscodingJob.RESULT_SUCCESS);
}
// Tests transcoding to a uri in external files directory and expects success.
@Test
public void testTranscodingToExternalFilesDir() throws Exception {
Log.d(TAG, "Starting: testMediaTranscodeManager");
// Create a file Uri: file:///data/user/0/com.android.mediatranscodingtest/files/temp.mp4
Uri destinationUri = Uri.fromFile(new File(mContext.getExternalFilesDir(null), "temp.mp4"));
Log.i(TAG, "Transcoding to files dir: " + destinationUri);
testTranscodingWithExpectResult(mSourceHEVCVideoUri, destinationUri,
TranscodingJob.RESULT_SUCCESS);
}
@Test
public void testTranscodingOneVideo() throws Exception {
Log.d(TAG, "Starting: testMediaTranscodeManager");
testTranscodingWithExpectResult(mSourceHEVCVideoUri, mDestinationUri,
TranscodingJob.RESULT_SUCCESS);
}
}

View File

@@ -36,8 +36,8 @@ public class MediaTranscodingTestRunner extends InstrumentationTestRunner {
@Override
public TestSuite getAllTests() {
TestSuite suite = new InstrumentationTestSuite(this);
suite.addTestSuite(MediaTranscodeManagerDiedTest.class);
suite.addTestSuite(MediaTranscodeManagerTest.class);
suite.addTestSuite(MediaTranscodeManagerWithMockServiceTest.class);
suite.addTestSuite(MediaTranscodingBenchmark.class);
return suite;
}