summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkeunyoung <keunyoung@google.com>2014-03-24 17:45:33 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2014-03-24 17:45:34 +0000
commit372c9f905d2819aab5e68473c4694b52a8ddf773 (patch)
tree168a552de7d0d5e5569b036abb5aabd972802939
parent651b3762335c04af38a3f5fbf3ba5ce74224682a (diff)
parentd51e0c83207dabadb0d64d86c2b4fff7ca9bef7f (diff)
downloadplatform_cts-372c9f905d2819aab5e68473c4694b52a8ddf773.tar.gz
platform_cts-372c9f905d2819aab5e68473c4694b52a8ddf773.tar.bz2
platform_cts-372c9f905d2819aab5e68473c4694b52a8ddf773.zip
Merge "improve EncodeVirtualDisplayWithCompositionTest to check rendered output" into klp-dev
-rw-r--r--tests/tests/media/AndroidManifest.xml6
-rw-r--r--tests/tests/media/res/layout/composition_layout.xml39
-rw-r--r--tests/tests/media/src/android/media/cts/CompositionTextureView.java129
-rw-r--r--tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java827
-rw-r--r--tests/tests/media/src/android/media/cts/OutputSurface.java29
-rw-r--r--tests/tests/media/src/android/media/cts/RemoteVirtualDisplayService.java226
6 files changed, 1128 insertions, 128 deletions
diff --git a/tests/tests/media/AndroidManifest.xml b/tests/tests/media/AndroidManifest.xml
index 2eeef0810f9..90024c46aec 100644
--- a/tests/tests/media/AndroidManifest.xml
+++ b/tests/tests/media/AndroidManifest.xml
@@ -58,6 +58,12 @@
<category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
</intent-filter>
</activity>
+ <service android:name="android.media.cts.RemoteVirtualDisplayService"
+ android:process=":remoteService" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ </intent-filter>
+ </service>
</application>
<instrumentation android:name="android.test.InstrumentationCtsTestRunner"
diff --git a/tests/tests/media/res/layout/composition_layout.xml b/tests/tests/media/res/layout/composition_layout.xml
new file mode 100644
index 00000000000..6aa9ea8b767
--- /dev/null
+++ b/tests/tests/media/res/layout/composition_layout.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2014 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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal" >
+ <FrameLayout
+ android:id="@+id/window0"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:visibility="visible"
+ android:layout_weight="0.33" />
+ <FrameLayout
+ android:id="@+id/window1"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ android:layout_weight="0.33" />
+ <FrameLayout
+ android:id="@+id/window2"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ android:layout_weight="0.33" />
+</LinearLayout>
diff --git a/tests/tests/media/src/android/media/cts/CompositionTextureView.java b/tests/tests/media/src/android/media/cts/CompositionTextureView.java
new file mode 100644
index 00000000000..5d84a11905d
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/CompositionTextureView.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2014 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 java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.TextureView;
+
+public class CompositionTextureView extends TextureView
+ implements TextureView.SurfaceTextureListener {
+ private static final String TAG = "CompositionTextureView";
+ private static final boolean DBG = true;
+ private static final boolean DBG_VERBOSE = false;
+
+ private final Semaphore mInitWaitSemaphore = new Semaphore(0);
+ private Surface mSurface;
+
+ public CompositionTextureView(Context context) {
+ super(context);
+ if (DBG) {
+ Log.i(TAG, "CompositionTextureView");
+ }
+ }
+
+ public CompositionTextureView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ if (DBG) {
+ Log.i(TAG, "CompositionTextureView");
+ }
+ }
+
+ public CompositionTextureView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ if (DBG) {
+ Log.i(TAG, "CompositionTextureView");
+ }
+ }
+
+ public void startListening() {
+ if (DBG) {
+ Log.i(TAG, "startListening");
+ }
+ setSurfaceTextureListener(this);
+ }
+
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture arg0, int arg1, int arg2) {
+ if (DBG) {
+ Log.i(TAG, "onSurfaceTextureAvailable");
+ }
+ recreateSurface(arg0);
+ mInitWaitSemaphore.release();
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture arg0) {
+ if (DBG) {
+ Log.i(TAG, "onSurfaceTextureDestroyed");
+ }
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture arg0, int arg1, int arg2) {
+ if (DBG) {
+ Log.i(TAG, "onSurfaceTextureSizeChanged");
+ }
+ recreateSurface(arg0);
+ mInitWaitSemaphore.release();
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture arg0) {
+ if (DBG_VERBOSE) {
+ Log.i(TAG, "onSurfaceTextureUpdated");
+ }
+ //ignore
+ }
+
+ public boolean waitForSurfaceReady(long timeoutMs) throws Exception {
+ if (isSurfaceTextureAvailable()) {
+ return true;
+ }
+ if (mInitWaitSemaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
+ return true;
+ }
+ return isSurfaceTextureAvailable();
+ }
+
+ private boolean isSurfaceTextureAvailable() {
+ SurfaceTexture surfaceTexture = getSurfaceTexture();
+ if (mSurface == null && surfaceTexture != null) {
+ recreateSurface(surfaceTexture);
+ }
+ return (mSurface != null);
+ }
+
+ private synchronized void recreateSurface(SurfaceTexture surfaceTexture) {
+ if (mSurface != null) {
+ mSurface.release();
+ }
+ mSurface = new Surface(surfaceTexture);
+ }
+
+ public Surface getSurface() {
+ return mSurface;
+ }
+
+}
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
index 79afc12b627..dc767c98b85 100644
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
@@ -18,12 +18,17 @@ package android.media.cts;
import android.app.Presentation;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.graphics.SurfaceTexture;
import android.graphics.Typeface;
+import android.graphics.drawable.ColorDrawable;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
import android.media.MediaCodecInfo;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCodecInfo.CodecProfileLevel;
@@ -34,22 +39,35 @@ import android.opengl.GLES20;
import android.opengl.Matrix;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcel;
import android.test.AndroidTestCase;
+import android.util.AttributeSet;
import android.util.Log;
+import android.util.Pair;
import android.view.Display;
import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
import android.widget.TextView;
+import com.android.cts.media.R;
+
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.Semaphore;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Tests to check if MediaCodec encoding works with composition of multiple virtual displays
@@ -61,11 +79,38 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
private static final String TAG = "EncodeVirtualDisplayWithCompositionTest";
private static final boolean DBG = false;
private static final String MIME_TYPE = "video/avc";
- private Handler mHandler;
- private Surface mSurface;
- private CodecInfo mCodecInfo;
+
+ private static final long DEFAULT_WAIT_TIMEOUT_MS = 5000;
+ private static final long DEFAULT_WAIT_TIMEOUT_US = 5000000;
+
+ private static final int COLOR_RED = makeColor(100, 0, 0);
+ private static final int COLOR_BLUE = makeColor(0, 100, 0);
+ private static final int COLOR_GREEN = makeColor(0, 0, 100);
+ private static final int COLOR_GREY = makeColor(100, 100, 100);
+
+ private static final int BITRATE_1080p = 20000000;
+ private static final int BITRATE_720p = 14000000;
+ private static final int BITRATE_800x480 = 14000000;
+ private static final int IFRAME_INTERVAL = 10;
+
+ private static final int MAX_NUM_WINDOWS = 3;
+
+ private static Handler sHandlerForRunOnMain = new Handler(Looper.getMainLooper());
+
+ private Surface mEncodingSurface;
+ private OutputSurface mDecodingSurface;
private volatile boolean mCodecConfigReceived = false;
private volatile boolean mCodecBufferReceived = false;
+ private EncodingHelper mEncodingHelper;
+ private MediaCodec mDecoder;
+ private final ByteBuffer mPixelBuf = ByteBuffer.allocateDirect(4);
+ private volatile boolean mIsQuitting = false;
+ private Throwable mTestException;
+ private VirtualDisplayPresentation mLocalPresentation;
+ private RemoteVirtualDisplayPresentation mRemotePresentation;
+ private ByteBuffer[] mDecoderInputBuffers;
+
+ /** event listener for test without verifying output */
private EncoderEventListener mEncoderEventListener = new EncoderEventListener() {
@Override
public void onCodecConfig(ByteBuffer data, MediaCodec.BufferInfo info) {
@@ -81,24 +126,280 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
}
};
- @Override
- protected void setUp() {
- mHandler = new Handler(Looper.getMainLooper());
- mCodecInfo = getAvcSupportedFormatInfo();
+ /* TEST_COLORS static initialization; need ARGB for ColorDrawable */
+ private static int makeColor(int red, int green, int blue) {
+ return 0xff << 24 | (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff);
+ }
+
+ public void testVirtualDisplayRecycles() throws Exception {
+ doTestVirtualDisplayRecycles(3);
+ }
+
+ public void testRendering800x480Locally() throws Throwable {
+ Log.i(TAG, "testRendering800x480Locally");
+ Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
+ if (maxRes.first >= 800 && maxRes.second >= 480) {
+ runTestRenderingInSeparateThread(800, 480, false, false);
+ } else {
+ Log.w(TAG, "This H/W does not support 800x480");
+ }
+ }
+
+ public void testRenderingMaxResolutionLocally() throws Throwable {
+ Log.i(TAG, "testRenderingMaxResolutionLocally");
+ Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
+ Log.w(TAG, "Trying resolution w:" + maxRes.first + " h:" + maxRes.second);
+ runTestRenderingInSeparateThread(maxRes.first, maxRes.second, false, false);
}
- public void testSingleVirtualDisplay() throws Exception {
- doTestVirtualDisplays(1);
+ public void testRendering800x480Remotely() throws Throwable {
+ Log.i(TAG, "testRendering800x480Remotely");
+ Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
+ if (maxRes.first >= 800 && maxRes.second >= 480) {
+ runTestRenderingInSeparateThread(800, 480, true, false);
+ } else {
+ Log.w(TAG, "This H/W does not support 800x480");
+ }
+ }
+
+ public void testRenderingMaxResolutionRemotely() throws Throwable {
+ Log.i(TAG, "testRenderingMaxResolutionRemotely");
+ Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
+ Log.w(TAG, "Trying resolution w:" + maxRes.first + " h:" + maxRes.second);
+ runTestRenderingInSeparateThread(maxRes.first, maxRes.second, true, false);
}
- public void testMultipleVirtualDisplays() throws Exception {
- doTestVirtualDisplays(3);
+ public void testRendering800x480RemotelyWith3Windows() throws Throwable {
+ Log.i(TAG, "testRendering800x480RemotelyWith3Windows");
+ Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
+ if (maxRes.first >= 800 && maxRes.second >= 480) {
+ runTestRenderingInSeparateThread(800, 480, true, true);
+ } else {
+ Log.w(TAG, "This H/W does not support 800x480");
+ }
+ }
+
+ public void testRendering800x480LocallyWith3Windows() throws Throwable {
+ Log.i(TAG, "testRendering800x480LocallyWith3Windows");
+ Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
+ if (maxRes.first >= 800 && maxRes.second >= 480) {
+ runTestRenderingInSeparateThread(800, 480, false, true);
+ } else {
+ Log.w(TAG, "This H/W does not support 800x480");
+ }
}
- void doTestVirtualDisplays(int numDisplays) throws Exception {
- final int NUM_CODEC_CREATION = 10;
- final int NUM_DISPLAY_CREATION = 20;
- final int NUM_RENDERING = 10;
+ /**
+ * Run rendering test in a separate thread. This is necessary as {@link OutputSurface} requires
+ * constructing it in a non-test thread.
+ * @param w
+ * @param h
+ * @throws Exception
+ */
+ private void runTestRenderingInSeparateThread(final int w, final int h,
+ final boolean runRemotely, final boolean multipleWindows) throws Throwable {
+ mTestException = null;
+ Thread renderingThread = new Thread(new Runnable() {
+ public void run() {
+ try {
+ doTestRenderingOutput(w, h, runRemotely, multipleWindows);
+ } catch (Throwable t) {
+ t.printStackTrace();
+ mTestException = t;
+ }
+ }
+ });
+ renderingThread.start();
+ renderingThread.join(20000);
+ assertTrue(!renderingThread.isAlive());
+ if (mTestException != null) {
+ throw mTestException;
+ }
+ }
+
+ private void doTestRenderingOutput(int w, int h, boolean runRemotely, boolean multipleWindows)
+ throws Throwable {
+ if (DBG) {
+ Log.i(TAG, "doTestRenderingOutput for w:" + w + " h:" + h);
+ }
+ try {
+ mIsQuitting = false;
+ mDecoder = MediaCodec.createDecoderByType(MIME_TYPE);
+ MediaFormat decoderFormat = MediaFormat.createVideoFormat(MIME_TYPE, w, h);
+ mDecodingSurface = new OutputSurface(w, h);
+ mDecoder.configure(decoderFormat, mDecodingSurface.getSurface(), null, 0);
+ mDecoder.start();
+ mDecoderInputBuffers = mDecoder.getInputBuffers();
+
+ mEncodingHelper = new EncodingHelper();
+ mEncodingSurface = mEncodingHelper.startEncoding(w, h,
+ new EncoderEventListener() {
+ @Override
+ public void onCodecConfig(ByteBuffer data, BufferInfo info) {
+ if (DBG) {
+ Log.i(TAG, "onCodecConfig l:" + info.size);
+ }
+ handleEncodedData(data, info);
+ }
+
+ @Override
+ public void onBufferReady(ByteBuffer data, BufferInfo info) {
+ if (DBG) {
+ Log.i(TAG, "onBufferReady l:" + info.size);
+ }
+ handleEncodedData(data, info);
+ }
+
+ @Override
+ public void onError(String errorMessage) {
+ fail(errorMessage);
+ }
+
+ private void handleEncodedData(ByteBuffer data, BufferInfo info) {
+ if (mIsQuitting) {
+ if (DBG) {
+ Log.i(TAG, "ignore data as test is quitting");
+ }
+ return;
+ }
+ int inputBufferIndex = mDecoder.dequeueInputBuffer(DEFAULT_WAIT_TIMEOUT_US);
+ if (inputBufferIndex < 0) {
+ if (DBG) {
+ Log.i(TAG, "dequeueInputBuffer returned:" + inputBufferIndex);
+ }
+ return;
+ }
+ assertTrue(inputBufferIndex >= 0);
+ ByteBuffer inputBuffer = mDecoderInputBuffers[inputBufferIndex];
+ inputBuffer.clear();
+ inputBuffer.put(data);
+ mDecoder.queueInputBuffer(inputBufferIndex, 0, info.size,
+ info.presentationTimeUs, info.flags);
+ }
+ });
+ GlCompositor compositor = new GlCompositor();
+ if (DBG) {
+ Log.i(TAG, "start composition");
+ }
+ compositor.startComposition(mEncodingSurface, w, h, multipleWindows ? 3 : 1);
+
+ if (DBG) {
+ Log.i(TAG, "create display");
+ }
+
+ Renderer renderer = null;
+ if (runRemotely) {
+ mRemotePresentation = new RemoteVirtualDisplayPresentation(getContext(),
+ compositor.getWindowSurface(multipleWindows? 1 : 0), w, h);
+ mRemotePresentation.connect();
+ mRemotePresentation.start();
+ renderer = mRemotePresentation;
+ } else {
+ mLocalPresentation = new VirtualDisplayPresentation(getContext(),
+ compositor.getWindowSurface(multipleWindows? 1 : 0), w, h);
+ mLocalPresentation.createVirtualDisplay();
+ mLocalPresentation.createPresentation();
+ renderer = mLocalPresentation;
+ }
+
+ if (DBG) {
+ Log.i(TAG, "start rendering and check");
+ }
+ renderColorAndCheckResult(renderer, w, h, COLOR_RED);
+ renderColorAndCheckResult(renderer, w, h, COLOR_BLUE);
+ renderColorAndCheckResult(renderer, w, h, COLOR_GREEN);
+ renderColorAndCheckResult(renderer, w, h, COLOR_GREY);
+
+ mIsQuitting = true;
+ if (runRemotely) {
+ mRemotePresentation.disconnect();
+ } else {
+ mLocalPresentation.dismissPresentation();
+ mLocalPresentation.destroyVirtualDisplay();
+ }
+
+ compositor.stopComposition();
+ } finally {
+ if (mEncodingHelper != null) {
+ mEncodingHelper.stopEncoding();
+ mEncodingHelper = null;
+ }
+ if (mDecoder != null) {
+ mDecoder.stop();
+ mDecoder.release();
+ mDecoder = null;
+ }
+ if (mDecodingSurface != null) {
+ mDecodingSurface.release();
+ mDecodingSurface = null;
+ }
+ }
+ }
+
+ private static final int NUM_MAX_RETRY = 120;
+ private static final int IMAGE_WAIT_TIMEOUT_MS = 1000;
+
+ private void renderColorAndCheckResult(Renderer renderer, int w, int h,
+ int color) throws Exception {
+ BufferInfo info = new BufferInfo();
+ for (int i = 0; i < NUM_MAX_RETRY; i++) {
+ renderer.doRendering(color);
+ int bufferIndex = mDecoder.dequeueOutputBuffer(info, DEFAULT_WAIT_TIMEOUT_US);
+ if (DBG) {
+ Log.i(TAG, "decoder dequeueOutputBuffer returned " + bufferIndex);
+ }
+ if (bufferIndex < 0) {
+ continue;
+ }
+ mDecoder.releaseOutputBuffer(bufferIndex, true);
+ if (mDecodingSurface.checkForNewImage(IMAGE_WAIT_TIMEOUT_MS)) {
+ mDecodingSurface.drawImage();
+ if (checkSurfaceFrameColor(w, h, color)) {
+ Log.i(TAG, "color " + Integer.toHexString(color) + " matched");
+ return;
+ }
+ } else if(DBG) {
+ Log.i(TAG, "no rendering yet");
+ }
+ }
+ fail("Color did not match");
+ }
+
+ private boolean checkSurfaceFrameColor(int w, int h, int color) {
+ // Read a pixel from the center of the surface. Might want to read from multiple points
+ // and average them together.
+ int x = w / 2;
+ int y = h / 2;
+ GLES20.glReadPixels(x, y, 1, 1, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPixelBuf);
+ int r = mPixelBuf.get(0) & 0xff;
+ int g = mPixelBuf.get(1) & 0xff;
+ int b = mPixelBuf.get(2) & 0xff;
+
+ int redExpected = (color >> 16) & 0xff;
+ int greenExpected = (color >> 8) & 0xff;
+ int blueExpected = color & 0xff;
+ if (approxEquals(redExpected, r) && approxEquals(greenExpected, g)
+ && approxEquals(blueExpected, b)) {
+ return true;
+ }
+ Log.i(TAG, "expected 0x" + Integer.toHexString(color) + " got 0x"
+ + Integer.toHexString(makeColor(r, g, b)));
+ return false;
+ }
+
+ /**
+ * Determines if two color values are approximately equal.
+ */
+ private static boolean approxEquals(int expected, int actual) {
+ final int MAX_DELTA = 4;
+ return Math.abs(expected - actual) <= MAX_DELTA;
+ }
+
+ private static final int NUM_CODEC_CREATION = 5;
+ private static final int NUM_DISPLAY_CREATION = 10;
+ private static final int NUM_RENDERING = 10;
+ private void doTestVirtualDisplayRecycles(int numDisplays) throws Exception {
+ CodecInfo codecInfo = getAvcSupportedFormatInfo();
VirtualDisplayPresentation[] virtualDisplays = new VirtualDisplayPresentation[numDisplays];
for (int i = 0; i < NUM_CODEC_CREATION; i++) {
mCodecConfigReceived = false;
@@ -107,12 +408,13 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
Log.i(TAG, "start encoding");
}
EncodingHelper encodingHelper = new EncodingHelper();
- mSurface = encodingHelper.startEncoding(mCodecInfo, mEncoderEventListener);
+ mEncodingSurface = encodingHelper.startEncoding(codecInfo.mMaxW, codecInfo.mMaxH,
+ mEncoderEventListener);
GlCompositor compositor = new GlCompositor();
if (DBG) {
Log.i(TAG, "start composition");
}
- compositor.startComposition(mSurface, mCodecInfo.mMaxW, mCodecInfo.mMaxH,
+ compositor.startComposition(mEncodingSurface, codecInfo.mMaxW, codecInfo.mMaxH,
numDisplays);
for (int j = 0; j < NUM_DISPLAY_CREATION; j++) {
if (DBG) {
@@ -122,8 +424,7 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
virtualDisplays[k] =
new VirtualDisplayPresentation(getContext(),
compositor.getWindowSurface(k),
- mCodecInfo.mMaxW/numDisplays, mCodecInfo.mMaxH,
- VirtualDisplayPresentation.RENDERING_VIEW_HIERARCHY);
+ codecInfo.mMaxW/numDisplays, codecInfo.mMaxH);
virtualDisplays[k].createVirtualDisplay();
virtualDisplays[k].createPresentation();
}
@@ -132,7 +433,7 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
}
for (int k = 0; k < NUM_RENDERING; k++) {
for (int l = 0; l < numDisplays; l++) {
- virtualDisplays[l].doRendering();
+ virtualDisplays[l].doRendering(COLOR_RED);
}
// do not care how many frames are actually rendered.
Thread.sleep(1);
@@ -166,14 +467,16 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
private MediaCodec mEncoder;
private volatile boolean mStopEncoding = false;
private EncoderEventListener mEventListener;
- private CodecInfo mCodecInfo;
+ private int mW;
+ private int mH;
private Thread mEncodingThread;
- private Surface mSurface;
- private static final int IFRAME_INTERVAL = 10;
+ private Surface mEncodingSurface;
private Semaphore mInitCompleted = new Semaphore(0);
- Surface startEncoding(CodecInfo codecInfo, EncoderEventListener eventListener) {
- mCodecInfo = codecInfo;
+ Surface startEncoding(int w, int h, EncoderEventListener eventListener) {
+ mStopEncoding = false;
+ mW = w;
+ mH = h;
mEventListener = eventListener;
mEncodingThread = new Thread(new Runnable() {
@Override
@@ -181,6 +484,7 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
try {
doEncoding();
} catch (Exception e) {
+ e.printStackTrace();
mEventListener.onError(e.toString());
}
}
@@ -197,7 +501,7 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
} catch (InterruptedException e) {
fail("should not happen");
}
- return mSurface;
+ return mEncodingSurface;
}
void stopEncoding() {
@@ -213,25 +517,36 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
private void doEncoding() throws Exception {
final int TIMEOUT_USEC_NORMAL = 1000000;
- MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mCodecInfo.mMaxW,
- mCodecInfo.mMaxH);
+ MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mW, mH);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
- format.setInteger(MediaFormat.KEY_BIT_RATE, mCodecInfo.mBitRate);
- format.setInteger(MediaFormat.KEY_FRAME_RATE, mCodecInfo.mFps);
+ int bitRate = 10000000;
+ if (mW == 1920 && mH == 1080) {
+ bitRate = BITRATE_1080p;
+ } else if (mW == 1280 && mH == 720) {
+ bitRate = BITRATE_720p;
+ } else if (mW == 800 && mH == 480) {
+ bitRate = BITRATE_800x480;
+ }
+ format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
- // Create a MediaCodec for the desired codec, then configure it as an encoder with
- // our desired properties. Request a Surface to use for input.
- mEncoder = MediaCodec.createByCodecName(mCodecInfo.mCodecName);
+ mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
- mSurface = mEncoder.createInputSurface();
+ mEncodingSurface = mEncoder.createInputSurface();
mEncoder.start();
mInitCompleted.release();
+ if (DBG) {
+ Log.i(TAG, "starting encoder");
+ }
try {
ByteBuffer[] encoderOutputBuffers = mEncoder.getOutputBuffers();
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
while (!mStopEncoding) {
int index = mEncoder.dequeueOutputBuffer(info, TIMEOUT_USEC_NORMAL);
+ if (DBG) {
+ Log.i(TAG, "dequeOutputBuffer returned " + index);
+ }
if (index >= 0) {
if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
Log.i(TAG, "codec config data");
@@ -241,6 +556,7 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
mEventListener.onCodecConfig(encodedData, info);
mEncoder.releaseOutputBuffer(index, false);
} else if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ Log.i(TAG, "EOS, stopping encoding");
break;
} else {
ByteBuffer encodedData = encoderOutputBuffers[index];
@@ -251,10 +567,15 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
}
}
}
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw e;
} finally {
mEncoder.stop();
mEncoder.release();
mEncoder = null;
+ mEncodingSurface.release();
+ mEncodingSurface = null;
}
}
}
@@ -266,10 +587,8 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
private Surface mSurface;
private int mWidth;
private int mHeight;
- private int mNumWindows;
- private ArrayList<GlWindow> mWindows = new ArrayList<GlWindow>();
- private HashMap<SurfaceTexture, GlWindow> mSurfaceTextureToWindowMap =
- new HashMap<SurfaceTexture, GlWindow>();
+ private volatile int mNumWindows;
+ private GlWindow mTopWindow;
private Thread mCompositionThread;
private Semaphore mStartCompletionSemaphore;
private Semaphore mRecreationCompletionSemaphore;
@@ -282,6 +601,7 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
private int mGlaPositionHandle;
private int mGlaTextureHandle;
private float[] mMVPMatrix = new float[16];
+ private TopWindowVirtualDisplayPresentation mTopPresentation;
private static final String VERTEX_SHADER =
"uniform mat4 uMVPMatrix;\n" +
@@ -303,7 +623,7 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
" gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
"}\n";
- void startComposition(Surface surface, int w, int h, int numWindows) {
+ void startComposition(Surface surface, int w, int h, int numWindows) throws Exception {
mSurface = surface;
mWidth = w;
mHeight = h;
@@ -329,14 +649,18 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
}
Surface getWindowSurface(int windowIndex) {
- return mWindows.get(windowIndex).getSurface();
+ return mTopPresentation.getSurface(windowIndex);
}
void recreateWindows() throws Exception {
mRecreationCompletionSemaphore = new Semaphore(0);
Message msg = mHandler.obtainMessage(CompositionHandler.DO_RECREATE_WINDOWS);
mHandler.sendMessage(msg);
- mRecreationCompletionSemaphore.acquire();
+ if(!mRecreationCompletionSemaphore.tryAcquire(DEFAULT_WAIT_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS)) {
+ fail("recreation timeout");
+ }
+ mTopPresentation.waitForSurfaceReady(DEFAULT_WAIT_TIMEOUT_MS);
}
@Override
@@ -344,16 +668,20 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
if (DBG) {
Log.i(TAG, "onFrameAvailable " + surface);
}
- GlWindow w = mSurfaceTextureToWindowMap.get(surface);
+ GlWindow w = mTopWindow;
if (w != null) {
w.markTextureUpdated();
requestUpdate();
} else {
- Log.w(TAG, "cannot map Surface " + surface + " to window");
+ Log.w(TAG, "top window gone");
}
}
private void requestUpdate() {
+ Thread compositionThread = mCompositionThread;
+ if (compositionThread == null || !compositionThread.isAlive()) {
+ return;
+ }
Message msg = mHandler.obtainMessage(CompositionHandler.DO_RENDERING);
mHandler.sendMessage(msg);
}
@@ -442,25 +770,27 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
Matrix.orthoM(projMatrix, 0, -wMid, wMid, -hMid, hMid, 1, 10);
Matrix.multiplyMM(mMVPMatrix, 0, projMatrix, 0, vMatrix, 0);
createWindows();
+
}
private void createWindows() throws GlException {
- // windows placed horizontally
- int windowWidth = mWidth / mNumWindows;
- for (int i = 0; i < mNumWindows; i++) {
- GlWindow window = new GlWindow(this, i * windowWidth, 0, windowWidth, mHeight);
- window.init();
- mSurfaceTextureToWindowMap.put(window.getSurfaceTexture(), window);
- mWindows.add(window);
- }
+ mTopWindow = new GlWindow(this, 0, 0, mWidth, mHeight);
+ mTopWindow.init();
+ mTopPresentation = new TopWindowVirtualDisplayPresentation(mContext,
+ mTopWindow.getSurface(), mWidth, mHeight, mNumWindows);
+ mTopPresentation.createVirtualDisplay();
+ mTopPresentation.createPresentation();
+ ((TopWindowPresentation) mTopPresentation.getPresentation()).populateWindows();
}
private void cleanupGl() {
- for (GlWindow w: mWindows) {
- w.cleanup();
+ if (mTopPresentation != null) {
+ mTopPresentation.dismissPresentation();
+ mTopPresentation.destroyVirtualDisplay();
+ }
+ if (mTopWindow != null) {
+ mTopWindow.cleanup();
}
- mWindows.clear();
- mSurfaceTextureToWindowMap.clear();
if (mEglHelper != null) {
mEglHelper.release();
}
@@ -470,52 +800,48 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
if (DBG) {
Log.i(TAG, "doGlRendering");
}
- for (GlWindow w: mWindows) {
- w.updateTexImageIfNecessary();
- }
+ mTopWindow.updateTexImageIfNecessary();
GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glUseProgram(mGlProgramId);
- for (GlWindow w: mWindows) {
- GLES20.glUniformMatrix4fv(mGluMVPMatrixHandle, 1, false, mMVPMatrix, 0);
- w.onDraw(mGluSTMatrixHandle, mGlaPositionHandle, mGlaTextureHandle);
- checkGlError("window draw");
- }
+ GLES20.glUniformMatrix4fv(mGluMVPMatrixHandle, 1, false, mMVPMatrix, 0);
+ mTopWindow.onDraw(mGluSTMatrixHandle, mGlaPositionHandle, mGlaTextureHandle);
+ checkGlError("window draw");
mEglHelper.swapBuffers();
}
+
private void doRecreateWindows() throws GlException {
- for (GlWindow w: mWindows) {
- w.cleanup();
- }
- mWindows.clear();
- mSurfaceTextureToWindowMap.clear();
+ mTopPresentation.dismissPresentation();
+ mTopPresentation.destroyVirtualDisplay();
+ mTopWindow.cleanup();
createWindows();
mRecreationCompletionSemaphore.release();
}
- private void waitForStartCompletion() {
- try {
- mStartCompletionSemaphore.acquire();
- } catch (InterruptedException e) {
- //ignore
+ private void waitForStartCompletion() throws Exception {
+ if (!mStartCompletionSemaphore.tryAcquire(DEFAULT_WAIT_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS)) {
+ fail("start timeout");
}
mStartCompletionSemaphore = null;
+ mTopPresentation.waitForSurfaceReady(DEFAULT_WAIT_TIMEOUT_MS);
}
private class CompositionRunnable implements Runnable {
@Override
public void run() {
try {
- initGl();
- Looper.prepare();
mLooper = Looper.myLooper();
+ Looper.prepare();
mHandler = new CompositionHandler();
+ initGl();
// init done
mStartCompletionSemaphore.release();
Looper.loop();
} catch (GlException e) {
- // ignore and clean-up
+ e.printStackTrace();
+ fail("got gl exception");
} finally {
cleanupGl();
mHandler = null;
@@ -560,7 +886,7 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
private volatile Surface mSurface;
private FloatBuffer mVerticesData;
private float[] mSTMatrix = new float[16];
- private AtomicBoolean mTextureUpdated = new AtomicBoolean(false);
+ private AtomicInteger mNumTextureUpdated = new AtomicInteger(0);
private GlCompositor mCompositor;
/**
@@ -620,7 +946,7 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
}
public void cleanup() {
- mTextureUpdated.set(false);
+ mNumTextureUpdated.set(0);
if (mTextureId != 0) {
int[] textures = new int[] {
mTextureId
@@ -628,30 +954,38 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
GLES20.glDeleteTextures(1, textures, 0);
}
GLES20.glFinish();
- mSurface.release();
- mSurface = null;
- mSurfaceTexture.release();
- mSurfaceTexture = null;
+ if (mSurface != null) {
+ mSurface.release();
+ mSurface = null;
+ }
+ if (mSurfaceTexture != null) {
+ mSurfaceTexture.release();
+ mSurfaceTexture = null;
+ }
}
/**
* make texture as updated so that it can be updated in the next rendering.
*/
public void markTextureUpdated() {
- mTextureUpdated.set(true);
+ mNumTextureUpdated.incrementAndGet();
}
/**
* update texture for rendering if it is updated.
*/
public void updateTexImageIfNecessary() {
- if (mTextureUpdated.getAndSet(false)) {
+ int numTextureUpdated = mNumTextureUpdated.getAndDecrement();
+ if (numTextureUpdated > 0) {
if (DBG) {
Log.i(TAG, "updateTexImageIfNecessary " + this);
}
mSurfaceTexture.updateTexImage();
mSurfaceTexture.getTransformMatrix(mSTMatrix);
}
+ if (numTextureUpdated < 0) {
+ fail("should not happen");
+ }
}
/**
@@ -701,26 +1035,24 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
}
}
- private class VirtualDisplayPresentation {
- public static final int RENDERING_OPENGL = 0;
- public static final int RENDERING_VIEW_HIERARCHY = 1;
+ private interface Renderer {
+ void doRendering(final int color) throws Exception;
+ }
- private Context mContext;
- private Surface mSurface;
- private int mWidth;
- private int mHeight;
- private int mRenderingType;
+ private static class VirtualDisplayPresentation implements Renderer {
+ protected final Context mContext;
+ protected final Surface mSurface;
+ protected final int mWidth;
+ protected final int mHeight;
+ protected VirtualDisplay mVirtualDisplay;
+ protected TestPresentationBase mPresentation;
private final DisplayManager mDisplayManager;
- private VirtualDisplay mVirtualDisplay;
- private TestPresentation mPresentation;
- VirtualDisplayPresentation(Context context, Surface surface, int w, int h,
- int renderingType) {
+ VirtualDisplayPresentation(Context context, Surface surface, int w, int h) {
mContext = context;
mSurface = surface;
mWidth = w;
mHeight = h;
- mRenderingType = renderingType;
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
}
@@ -747,13 +1079,20 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
runOnMainSync(new Runnable() {
@Override
public void run() {
- mPresentation = new TestPresentation(getContext(),
- mVirtualDisplay.getDisplay());
+ mPresentation = doCreatePresentation();
mPresentation.show();
}
});
}
+ protected TestPresentationBase doCreatePresentation() {
+ return new TestPresentation(mContext, mVirtualDisplay.getDisplay());
+ }
+
+ TestPresentationBase getPresentation() {
+ return mPresentation;
+ }
+
void dismissPresentation() {
runOnMainSync(new Runnable() {
@Override
@@ -763,47 +1102,193 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
});
}
- void doRendering() {
+ @Override
+ public void doRendering(final int color) throws Exception {
runOnMainSync(new Runnable() {
@Override
public void run() {
- mPresentation.doRendering();
+ mPresentation.doRendering(color);
}
});
}
+ }
+
+ private static class TestPresentationBase extends Presentation {
+
+ public TestPresentationBase(Context outerContext, Display display) {
+ super(outerContext, display);
+ getWindow().setType(WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ }
+
+ public void doRendering(int color) {
+ // to be implemented by child
+ }
+ }
+
+ private static class TestPresentation extends TestPresentationBase {
+ private ImageView mImageView;
+
+ public TestPresentation(Context outerContext, Display display) {
+ super(outerContext, display);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mImageView = new ImageView(getContext());
+ mImageView.setImageDrawable(new ColorDrawable(COLOR_RED));
+ mImageView.setLayoutParams(new LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ setContentView(mImageView);
+ }
- private class TestPresentation extends Presentation {
- private TextView mTextView;
- private int mRenderingCount = 0;
+ public void doRendering(int color) {
+ mImageView.setImageDrawable(new ColorDrawable(color));
+ }
+ }
+
+ private static class TopWindowPresentation extends TestPresentationBase {
+ private FrameLayout[] mWindowsLayout = new FrameLayout[MAX_NUM_WINDOWS];
+ private CompositionTextureView[] mWindows = new CompositionTextureView[MAX_NUM_WINDOWS];
+ private final int mNumWindows;
+ private final Semaphore mWindowWaitSemaphore = new Semaphore(0);
+
+ public TopWindowPresentation(int numWindows, Context outerContext, Display display) {
+ super(outerContext, display);
+ mNumWindows = numWindows;
+ }
- public TestPresentation(Context outerContext, Display display) {
- super(outerContext, display);
- getWindow().setType(WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (DBG) {
+ Log.i(TAG, "TopWindowPresentation onCreate, numWindows " + mNumWindows);
}
+ setContentView(R.layout.composition_layout);
+ mWindowsLayout[0] = (FrameLayout) findViewById(R.id.window0);
+ mWindowsLayout[1] = (FrameLayout) findViewById(R.id.window1);
+ mWindowsLayout[2] = (FrameLayout) findViewById(R.id.window2);
+ }
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (VirtualDisplayPresentation.this.mRenderingType == RENDERING_OPENGL) {
- //TODO add init for opengl renderer
- } else {
- mTextView = new TextView(getContext());
- mTextView.setTextSize(14);
- mTextView.setTypeface(Typeface.DEFAULT_BOLD);
- mTextView.setText(Integer.toString(mRenderingCount));
- setContentView(mTextView);
+ public void populateWindows() {
+ runOnMain(new Runnable() {
+ public void run() {
+ for (int i = 0; i < mNumWindows; i++) {
+ mWindows[i] = new CompositionTextureView(getContext());
+ mWindows[i].setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ mWindowsLayout[i].setVisibility(View.VISIBLE);
+ mWindowsLayout[i].addView(mWindows[i]);
+ mWindows[i].startListening();
+ }
+ mWindowWaitSemaphore.release();
}
- }
+ });
+ }
- public void doRendering() {
- if (VirtualDisplayPresentation.this.mRenderingType == RENDERING_OPENGL) {
- //TODO add opengl rendering
- } else {
- mRenderingCount++;
- mTextView.setText(Integer.toString(mRenderingCount));
+ public void waitForSurfaceReady(long timeoutMs) throws Exception {
+ mWindowWaitSemaphore.tryAcquire(DEFAULT_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ for (int i = 0; i < mNumWindows; i++) {
+ if(!mWindows[i].waitForSurfaceReady(timeoutMs)) {
+ fail("surface wait timeout");
}
}
}
+
+ public Surface getSurface(int windowIndex) {
+ Surface surface = mWindows[windowIndex].getSurface();
+ assertNotNull(surface);
+ return surface;
+ }
+ }
+
+ private static class TopWindowVirtualDisplayPresentation extends VirtualDisplayPresentation {
+ private final int mNumWindows;
+
+ TopWindowVirtualDisplayPresentation(Context context, Surface surface, int w, int h,
+ int numWindows) {
+ super(context, surface, w, h);
+ assertNotNull(surface);
+ mNumWindows = numWindows;
+ }
+
+ void waitForSurfaceReady(long timeoutMs) throws Exception {
+ ((TopWindowPresentation) mPresentation).waitForSurfaceReady(timeoutMs);
+ }
+
+ Surface getSurface(int windowIndex) {
+ return ((TopWindowPresentation) mPresentation).getSurface(windowIndex);
+ }
+
+ protected TestPresentationBase doCreatePresentation() {
+ return new TopWindowPresentation(mNumWindows, mContext, mVirtualDisplay.getDisplay());
+ }
+ }
+
+ private static class RemoteVirtualDisplayPresentation implements Renderer {
+ /** argument: Surface, int w, int h, return none */
+ private static final int BINDER_CMD_START = IBinder.FIRST_CALL_TRANSACTION;
+ /** argument: int color, return none */
+ private static final int BINDER_CMD_RENDER = IBinder.FIRST_CALL_TRANSACTION + 1;
+
+ private final Context mContext;
+ private final Surface mSurface;
+ private final int mWidth;
+ private final int mHeight;
+
+ private IBinder mService;
+ private final Semaphore mConnectionWait = new Semaphore(0);
+ private final ServiceConnection mConnection = new ServiceConnection() {
+
+ public void onServiceConnected(ComponentName arg0, IBinder arg1) {
+ mService = arg1;
+ mConnectionWait.release();
+ }
+
+ public void onServiceDisconnected(ComponentName arg0) {
+ //ignore
+ }
+
+ };
+
+ RemoteVirtualDisplayPresentation(Context context, Surface surface, int w, int h) {
+ mContext = context;
+ mSurface = surface;
+ mWidth = w;
+ mHeight = h;
+ }
+
+ void connect() throws Exception {
+ Intent intent = new Intent();
+ intent.setClassName("com.android.cts.media",
+ "android.media.cts.RemoteVirtualDisplayService");
+ mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+ if (!mConnectionWait.tryAcquire(DEFAULT_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("cannot bind to service");
+ }
+ }
+
+ void disconnect() {
+ mContext.unbindService(mConnection);
+ }
+
+ void start() throws Exception {
+ Parcel parcel = Parcel.obtain();
+ mSurface.writeToParcel(parcel, 0);
+ parcel.writeInt(mWidth);
+ parcel.writeInt(mHeight);
+ mService.transact(BINDER_CMD_START, parcel, null, 0);
+ }
+
+ @Override
+ public void doRendering(int color) throws Exception {
+ Parcel parcel = Parcel.obtain();
+ parcel.writeInt(color);
+ mService.transact(BINDER_CMD_RENDER, parcel, null, 0);
+ }
}
private static class CodecInfo {
@@ -813,6 +1298,7 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
public int mBitRate;
public String mCodecName;
};
+
/**
* Returns the first codec capable of encoding the specified MIME type, or null if no
* match was found.
@@ -911,9 +1397,93 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
return info;
}
- public void runOnMainSync(Runnable runner) {
+ /**
+ * Check maximum concurrent encoding / decoding resolution allowed.
+ * Some H/Ws cannot support maximum resolution reported in encoder if decoder is running
+ * at the same time.
+ * Check is done for 4 different levels: 1080p, 720p, 800x480 and max of encoder if is is
+ * smaller than 800x480.
+ */
+ private Pair<Integer, Integer> checkMaxConcurrentEncodingDecodingResolution() {
+ CodecInfo codecInfo = getAvcSupportedFormatInfo();
+ int maxW = codecInfo.mMaxW;
+ int maxH = codecInfo.mMaxH;
+ if (maxW >= 1920 && maxH >= 1080) {
+ if (isConcurrentEncodingDecodingSupported(1920, 1080, BITRATE_1080p)) {
+ return new Pair<Integer, Integer>(1920, 1080);
+ }
+ }
+ if (maxW >= 1280 && maxH >= 720) {
+ if (isConcurrentEncodingDecodingSupported(1280, 720, BITRATE_720p)) {
+ return new Pair<Integer, Integer>(1280, 720);
+ }
+ }
+ if (maxW >= 800 && maxH >= 480) {
+ if (isConcurrentEncodingDecodingSupported(800, 480, BITRATE_800x480)) {
+ return new Pair<Integer, Integer>(800, 480);
+ }
+ }
+ if (!isConcurrentEncodingDecodingSupported(codecInfo.mMaxW, codecInfo.mMaxH,
+ codecInfo.mBitRate)) {
+ fail("should work with advertised resolution");
+ }
+ return new Pair<Integer, Integer>(maxW, maxH);
+ }
+
+ private boolean isConcurrentEncodingDecodingSupported(int w, int h, int bitRate) {
+ MediaCodec decoder = null;
+ OutputSurface decodingSurface = null;
+ MediaCodec encoder = null;
+ Surface encodingSurface = null;
+ try {
+ decoder = MediaCodec.createDecoderByType(MIME_TYPE);
+ MediaFormat decoderFormat = MediaFormat.createVideoFormat(MIME_TYPE, w, h);
+ decodingSurface = new OutputSurface(w, h);
+ decodingSurface.makeCurrent();
+ decoder.configure(decoderFormat, decodingSurface.getSurface(), null, 0);
+ decoder.start();
+
+ MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, w, h);
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+ MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+ encoder = MediaCodec.createEncoderByType(MIME_TYPE);;
+ encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ encodingSurface = encoder.createInputSurface();
+ encoder.start();
+
+ encoder.stop();
+ decoder.stop();
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.i(TAG, "This H/W does not support w:" + w + " h:" + h);
+ return false;
+ } finally {
+ if (encodingSurface != null) {
+ encodingSurface.release();
+ }
+ if (encoder != null) {
+ encoder.release();
+ }
+ if (decoder != null) {
+ decoder.release();
+ }
+ if (decodingSurface != null) {
+ decodingSurface.release();
+ }
+ }
+ return true;
+ }
+
+ private static void runOnMain(Runnable runner) {
+ sHandlerForRunOnMain.post(runner);
+ }
+
+ private static void runOnMainSync(Runnable runner) {
SyncRunnable sr = new SyncRunnable(runner);
- mHandler.post(sr);
+ sHandlerForRunOnMain.post(sr);
sr.waitForComplete();
}
@@ -939,6 +1509,7 @@ public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
try {
wait();
} catch (InterruptedException e) {
+ //ignore
}
}
}
diff --git a/tests/tests/media/src/android/media/cts/OutputSurface.java b/tests/tests/media/src/android/media/cts/OutputSurface.java
index fd36d80c1f8..fc8ad9cd390 100644
--- a/tests/tests/media/src/android/media/cts/OutputSurface.java
+++ b/tests/tests/media/src/android/media/cts/OutputSurface.java
@@ -250,6 +250,35 @@ class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
}
/**
+ * Wait up to given timeout until new image become available.
+ * @param timeoutMs
+ * @return true if new image is available. false for no new image until timeout.
+ */
+ public boolean checkForNewImage(int timeoutMs) {
+ synchronized (mFrameSyncObject) {
+ while (!mFrameAvailable) {
+ try {
+ // Wait for onFrameAvailable() to signal us. Use a timeout to avoid
+ // stalling the test if it doesn't arrive.
+ mFrameSyncObject.wait(timeoutMs);
+ if (!mFrameAvailable) {
+ return false;
+ }
+ } catch (InterruptedException ie) {
+ // shouldn't happen
+ throw new RuntimeException(ie);
+ }
+ }
+ mFrameAvailable = false;
+ }
+
+ // Latch the data.
+ mTextureRender.checkGlError("before updateTexImage");
+ mSurfaceTexture.updateTexImage();
+ return true;
+ }
+
+ /**
* Draws the data from SurfaceTexture onto the current EGL surface.
*/
public void drawImage() {
diff --git a/tests/tests/media/src/android/media/cts/RemoteVirtualDisplayService.java b/tests/tests/media/src/android/media/cts/RemoteVirtualDisplayService.java
new file mode 100644
index 00000000000..662cd5be123
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/RemoteVirtualDisplayService.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2014 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.app.Presentation;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.ColorDrawable;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Display;
+import android.view.Surface;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+public class RemoteVirtualDisplayService extends Service {
+ private static final String TAG = "RemoteVirtualDisplayService";
+
+ /** argument: Surface, int w, int h, return none */
+ private static final int BINDER_CMD_START = IBinder.FIRST_CALL_TRANSACTION;
+ /** argument: int color, return none */
+ private static final int BINDER_CMD_RENDER = IBinder.FIRST_CALL_TRANSACTION + 1;
+ private final Handler mHandlerForRunOnMain = new Handler(Looper.getMainLooper());;
+ private IBinder mBinder;
+ private VirtualDisplayPresentation mPresentation;
+
+ @Override
+ public void onCreate() {
+ Log.i(TAG, "onCreate");
+ mBinder = new Binder() {
+ @Override
+ protected boolean onTransact(int code, Parcel data, Parcel reply,
+ int flags) throws RemoteException {
+ switch(code) {
+ case BINDER_CMD_START: {
+ Surface surface = Surface.CREATOR.createFromParcel(data);
+ int w = data.readInt();
+ int h = data.readInt();
+ start(surface, w, h);
+ break;
+ }
+ case BINDER_CMD_RENDER: {
+ int color = data.readInt();
+ render(color);
+ break;
+ }
+ default:
+ Log.e(TAG, "unrecognized binder command " + code);
+ return false;
+ }
+ return true;
+ }
+ };
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.i(TAG, "onBind");
+ return mBinder;
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.i(TAG, "onDestroy");
+ if (mPresentation != null) {
+ mPresentation.dismissPresentation();
+ mPresentation.destroyVirtualDisplay();
+ mPresentation = null;
+ }
+ }
+
+ private void start(Surface surface, int w, int h) {
+ Log.i(TAG, "start");
+ mPresentation = new VirtualDisplayPresentation(this, surface, w, h);
+ mPresentation.createVirtualDisplay();
+ mPresentation.createPresentation();
+ }
+
+ private void render(int color) {
+ Log.i(TAG, "render");
+ mPresentation.doRendering(color);
+ }
+
+ private class VirtualDisplayPresentation {
+ private Context mContext;
+ private Surface mSurface;
+ private int mWidth;
+ private int mHeight;
+ private final DisplayManager mDisplayManager;
+ private VirtualDisplay mVirtualDisplay;
+ private TestPresentation mPresentation;
+
+ VirtualDisplayPresentation(Context context, Surface surface, int w, int h) {
+ mContext = context;
+ mSurface = surface;
+ mWidth = w;
+ mHeight = h;
+ mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
+ }
+
+ void createVirtualDisplay() {
+ runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mVirtualDisplay = mDisplayManager.createVirtualDisplay(
+ TAG, mWidth, mHeight, 200, mSurface, 0);
+ }
+ });
+ }
+
+ void destroyVirtualDisplay() {
+ if (mVirtualDisplay != null) {
+ mVirtualDisplay.release();
+ }
+ }
+
+ void createPresentation() {
+ runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mPresentation = new TestPresentation(RemoteVirtualDisplayService.this,
+ mVirtualDisplay.getDisplay());
+ mPresentation.show();
+ }
+ });
+ }
+
+ void dismissPresentation() {
+ if (mPresentation != null) {
+ mPresentation.dismiss();
+ }
+ }
+
+ public void doRendering(final int color) {
+ runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mPresentation.doRendering(color);
+ }
+ });
+ }
+
+ private class TestPresentation extends Presentation {
+ private ImageView mImageView;
+
+ public TestPresentation(Context outerContext, Display display) {
+ super(outerContext, display);
+ getWindow().setType(WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mImageView = new ImageView(getContext());
+ mImageView.setImageDrawable(new ColorDrawable(0));
+ mImageView.setLayoutParams(new LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ setContentView(mImageView);
+ }
+
+ public void doRendering(int color) {
+ mImageView.setImageDrawable(new ColorDrawable(color));
+ }
+ }
+ }
+
+ private void runOnMainSync(Runnable runner) {
+ SyncRunnable sr = new SyncRunnable(runner);
+ mHandlerForRunOnMain.post(sr);
+ sr.waitForComplete();
+ }
+
+ private static final class SyncRunnable implements Runnable {
+ private final Runnable mTarget;
+ private boolean mComplete;
+
+ public SyncRunnable(Runnable target) {
+ mTarget = target;
+ }
+
+ public void run() {
+ mTarget.run();
+ synchronized (this) {
+ mComplete = true;
+ notifyAll();
+ }
+ }
+
+ public void waitForComplete() {
+ synchronized (this) {
+ while (!mComplete) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ //ignore
+ }
+ }
+ }
+ }
+ }
+}