Video chat camera test app: use SurfaceTexture on Honeycomb+.
Change-Id: I5b1759c521d193356dc2e87d07a05a372cc1c995
diff --git a/CameraPreviewTest/res/layout/videochatcameratest_activity.xml b/CameraPreviewTest/res/layout/videochatcameratest_activity.xml
index cebff29..95cced5 100644
--- a/CameraPreviewTest/res/layout/videochatcameratest_activity.xml
+++ b/CameraPreviewTest/res/layout/videochatcameratest_activity.xml
@@ -21,20 +21,9 @@
android:layout_width="match_parent" android:layout_height="match_parent"
android:orientation="vertical">
- <LinearLayout
- android:layout_width="wrap_content" android:layout_height="wrap_content"
- android:orientation="horizontal">
-
- <com.example.android.videochatcameratest.CameraPreviewView android:id="@+id/previewrender"
- android:layout_width="320px" android:layout_height="240px"
- />
-
-<!--
- <com.example.android.videochatcameratest.RenderView android:id="@+id/render"
- android:layout_width="320px" android:layout_height="240px" />
--->
-
- </LinearLayout>
+ <FrameLayout android:id="@+id/previewFrame"
+ android:layout_width="320px" android:layout_height="240px"
+ android:orientation="horizontal" />
<ScrollView android:id="@+id/scrollviewforhistory"
android:layout_width="fill_parent"
diff --git a/CameraPreviewTest/src/com/example/android/videochatcameratest/SurfaceTextureView.java b/CameraPreviewTest/src/com/example/android/videochatcameratest/SurfaceTextureView.java
new file mode 100644
index 0000000..f8e378a
--- /dev/null
+++ b/CameraPreviewTest/src/com/example/android/videochatcameratest/SurfaceTextureView.java
@@ -0,0 +1,246 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+
+package com.example.android.videochatcameratest;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.graphics.SurfaceTexture.OnFrameAvailableListener;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+class SurfaceTextureView extends GLSurfaceView {
+ static final private String TAG = "VideoChatTest";
+
+ private int mTextureName;
+ private SurfaceTexture mSurfaceTexture;
+ public int getTextureName() {
+ return mTextureName;
+ }
+ public SurfaceTexture getSurfaceTexture() {
+ return mSurfaceTexture;
+ }
+
+ public static int loadShader(int shaderType, String source) {
+ int shader = GLES20.glCreateShader(shaderType);
+ if (shader != 0) {
+ GLES20.glShaderSource(shader, source);
+ GLES20.glCompileShader(shader);
+ int[] compiled = new int[1];
+ GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
+ if (compiled[0] == 0) {
+ Log.e(TAG, "Could not compile shader " + shaderType + ":");
+ Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
+ GLES20.glDeleteShader(shader);
+ shader = 0;
+ }
+ }
+ return shader;
+ }
+
+ public static void checkGlError(String op) {
+ int error;
+ while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+ Log.e(TAG, op + ": glError " + error);
+ throw new RuntimeException(op + ": glError " + error);
+ }
+ }
+
+ public static int createProgram(String vertexSource, String fragmentSource) {
+ int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
+ if (vertexShader == 0) {
+ return 0;
+ }
+ int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
+ if (pixelShader == 0) {
+ return 0;
+ }
+
+ int program = GLES20.glCreateProgram();
+ if (program != 0) {
+ GLES20.glAttachShader(program, vertexShader);
+ checkGlError("glAttachShader");
+ GLES20.glAttachShader(program, pixelShader);
+ checkGlError("glAttachShader");
+ GLES20.glLinkProgram(program);
+ int[] linkStatus = new int[1];
+ GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
+ if (linkStatus[0] != GLES20.GL_TRUE) {
+ Log.e(TAG, "Could not link program: ");
+ Log.e(TAG, GLES20.glGetProgramInfoLog(program));
+ GLES20.glDeleteProgram(program);
+ program = 0;
+ }
+ }
+ return program;
+ }
+
+ AtomicInteger mReportedFrameCount = new AtomicInteger();
+ AtomicBoolean mCameraEnabled = new AtomicBoolean();
+ AtomicInteger mCameraFrameCount = new AtomicInteger();
+
+ /**
+ * @param context
+ */
+ public SurfaceTextureView(Context context) {
+ super(context);
+ init();
+ }
+
+ public SurfaceTextureView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ private void init() {
+ setEGLContextClientVersion(2);
+ setRenderer(new Renderer());
+ }
+
+ public void setCameraEnabled(boolean enabled) {
+ mCameraEnabled.set(enabled);
+ }
+
+ public void resetFrameCounter() {
+ mReportedFrameCount.set(0);
+ }
+
+ public int getFrameCounter() {
+ return mReportedFrameCount.get();
+ }
+
+ class Renderer implements GLSurfaceView.Renderer {
+ private final static String VERTEX_SHADER =
+ "attribute vec4 vPosition;\n" +
+ "attribute vec2 a_texCoord;\n" +
+ "varying vec2 v_texCoord;\n" +
+ "uniform mat4 u_xform;\n" +
+ "void main() {\n" +
+ " gl_Position = vPosition;\n" +
+ " v_texCoord = vec2(u_xform * vec4(a_texCoord, 1.0, 1.0));\n" +
+ "}\n";
+
+ private final static String FRAGMENT_SHADER =
+ "#extension GL_OES_EGL_image_external : require\n" +
+ "precision mediump float;\n" +
+ "uniform samplerExternalOES s_texture;\n" +
+ "varying vec2 v_texCoord;\n" +
+ "void main() {\n" +
+ " gl_FragColor = texture2D(s_texture, v_texCoord);\n" +
+ "}\n";
+
+ private final float[] TEXTURE_VERTICES =
+ { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f };
+
+ private final float[] QUAD_VERTICES =
+ { 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f };
+
+ private final static int FLOAT_SIZE_BYTES = 4;
+
+ private final FloatBuffer mTextureVertices;
+ private final FloatBuffer mQuadVertices;
+
+
+ private int mGLProgram;
+ private int mTexHandle;
+ private int mTexCoordHandle;
+ private int mTriangleVerticesHandle;
+ private int mTransformHandle;
+ private int mViewWidth;
+ private int mViewHeight;
+ private float[] mTransformMatrix;
+ private int mLastCameraFrameCount;
+ public Renderer() {
+ mTextureVertices = ByteBuffer.allocateDirect(TEXTURE_VERTICES.length *
+ FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
+ mTextureVertices.put(TEXTURE_VERTICES).position(0);
+ mQuadVertices = ByteBuffer.allocateDirect(QUAD_VERTICES.length *
+ FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
+ mQuadVertices.put(QUAD_VERTICES).position(0);
+ mTransformMatrix = new float[16];
+ mLastCameraFrameCount = mCameraFrameCount.get();
+ }
+
+ @Override
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ mGLProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
+
+ mTexHandle = GLES20.glGetUniformLocation(mGLProgram, "s_texture");
+ mTexCoordHandle = GLES20.glGetAttribLocation(mGLProgram, "a_texCoord");
+ mTriangleVerticesHandle = GLES20.glGetAttribLocation(mGLProgram, "vPosition");
+ mTransformHandle = GLES20.glGetUniformLocation(mGLProgram, "u_xform");
+ int[] textures = new int[1];
+ GLES20.glGenTextures(1, textures, 0);
+ mTextureName = textures[0];
+ GLES20.glUseProgram(mGLProgram);
+ GLES20.glVertexAttribPointer(mTexCoordHandle, 2, GLES20.GL_FLOAT,
+ false, 0, mTextureVertices);
+ GLES20.glVertexAttribPointer(mTriangleVerticesHandle, 2, GLES20.GL_FLOAT,
+ false, 0, mQuadVertices);
+ checkGlError("initialization");
+ mSurfaceTexture = new SurfaceTexture(mTextureName);
+ mSurfaceTexture.setOnFrameAvailableListener(new OnFrameAvailableListener() {
+ @Override
+ public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+ mCameraFrameCount.incrementAndGet();
+ }
+ });
+ }
+
+ /* (non-Javadoc)
+ * @see android.opengl.GLSurfaceView.Renderer#onSurfaceChanged(javax.microedition.khronos.opengles.GL10, int, int)
+ */
+ @Override
+ public void onSurfaceChanged(GL10 gl, int width, int height) {
+ mViewWidth = width;
+ mViewHeight = height;
+ }
+
+ private static final int GL_TEXTURE_EXTERNAL_OES = 0x8D65;
+ @Override
+ public void onDrawFrame(GL10 gl) {
+ GLES20.glUseProgram(mGLProgram);
+ GLES20.glViewport(0, 0, mViewWidth, mViewHeight);
+ checkGlError("glViewport");
+
+ if (mCameraEnabled.get()) {
+ int cameraFrameCount = mCameraFrameCount.get();
+ if (mLastCameraFrameCount != cameraFrameCount) {
+ mReportedFrameCount.incrementAndGet();
+ mSurfaceTexture.updateTexImage();
+ mSurfaceTexture.getTransformMatrix(mTransformMatrix);
+ GLES20.glUniformMatrix4fv(mTransformHandle, 1, false, mTransformMatrix, 0);
+ checkGlError("glUniformMatrix4fv");
+ mLastCameraFrameCount = cameraFrameCount;
+ }
+ GLES20.glDisable(GLES20.GL_BLEND);
+ checkGlError("setup");
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ checkGlError("setup");
+ GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureName);
+ checkGlError("setup");
+ GLES20.glUniform1i(mTexHandle, 0);
+ checkGlError("setup");
+ GLES20.glEnableVertexAttribArray(mTexCoordHandle);
+ checkGlError("setup");
+ GLES20.glEnableVertexAttribArray(mTriangleVerticesHandle);
+ checkGlError("setup");
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, 4);
+ checkGlError("glDrawArrays");
+ } else {
+ GLES20.glClearColor(0,0,0,0);
+ }
+ }
+
+ }
+}
diff --git a/CameraPreviewTest/src/com/example/android/videochatcameratest/VideoChatTestActivity.java b/CameraPreviewTest/src/com/example/android/videochatcameratest/VideoChatTestActivity.java
index 50879cf..033b8b2 100644
--- a/CameraPreviewTest/src/com/example/android/videochatcameratest/VideoChatTestActivity.java
+++ b/CameraPreviewTest/src/com/example/android/videochatcameratest/VideoChatTestActivity.java
@@ -24,10 +24,12 @@
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
+import android.view.TextureView;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
+import android.widget.FrameLayout;
import android.widget.TextView;
import java.io.IOException;
@@ -43,9 +45,14 @@
public class VideoChatTestActivity extends Activity {
static final private int NUM_CAMERA_PREVIEW_BUFFERS = 2;
-
+ static final boolean sRunningOnHoneycomb;
static final private String TAG = "VideoChatTest";
TextView mTextStatusHistory;
+ static {
+ sRunningOnHoneycomb =
+ android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB;
+ }
+
public VideoChatTestActivity() {
}
@@ -57,6 +64,14 @@
// Inflate our UI from its XML layout description.
setContentView(R.layout.videochatcameratest_activity);
+ FrameLayout fl = (FrameLayout)findViewById(R.id.previewFrame);
+
+ if (sRunningOnHoneycomb) {
+ fl.addView(new SurfaceTextureView(this));
+ } else {
+ fl.addView(new CameraPreviewView(this));
+ }
+
((Button) findViewById(R.id.gobutton)).setOnClickListener(mGoListener);
((TextView)findViewById(R.id.statushistory)).setVerticalScrollBarEnabled(true);
@@ -305,13 +320,24 @@
Log.v(TAG, "Start test -- id " + whichCamera + " " + width + "x" + height +
" " + frameRate + "fps");
Camera camera;
- CameraPreviewView previewView = (CameraPreviewView)findViewById(R.id.previewrender);
+ FrameLayout previewBlock = (FrameLayout)findViewById(R.id.previewFrame);
+ SurfaceTextureView surfaceTextureView = null;
+ CameraPreviewView previewView = null;
+ if (sRunningOnHoneycomb) {
+ surfaceTextureView = (SurfaceTextureView)previewBlock.getChildAt(0);
+ } else {
+ previewView = (CameraPreviewView)previewBlock.getChildAt(0);
+ }
camera = Camera.open(whichCamera);
publishProgress("Opened " + baseStatus);
try {
try {
- camera.setPreviewDisplay(previewView.mHolder);
+ if (sRunningOnHoneycomb) {
+ camera.setPreviewTexture(surfaceTextureView.getSurfaceTexture());
+ } else {
+ camera.setPreviewDisplay(previewView.mHolder);
+ }
} catch (IOException exception) {
succeeded = false;
status = exception.toString();
@@ -398,7 +424,12 @@
return;
}
}
- setupCallback(camera, catcher, bufferSize);
+ if (sRunningOnHoneycomb) {
+ surfaceTextureView.resetFrameCounter();
+ surfaceTextureView.setCameraEnabled(true);
+ } else {
+ setupCallback(camera, catcher, bufferSize);
+ }
camera.startPreview();
try {
Thread.sleep(5000);
@@ -407,16 +438,26 @@
status = exception.toString();
return;
}
- camera.setPreviewCallbackWithBuffer(null);
+ if (sRunningOnHoneycomb) {
+ surfaceTextureView.setCameraEnabled(false);
+ } else {
+ camera.setPreviewCallbackWithBuffer(null);
+ }
camera.stopPreview();
}
- if (catcher.mFrames == 0) {
+ int frames;
+ if (sRunningOnHoneycomb) {
+ frames = surfaceTextureView.getFrameCounter();
+ } else {
+ frames = catcher.mFrames;
+ }
+ if (frames == 0) {
succeeded = false;
publishProgress("Preview callback received no frames from " + baseStatus);
} else {
- publishProgress("Preview callback got " + catcher.mFrames + " frames (~" +
- Math.round(((double)catcher.mFrames)/(5.0 * numPasses)) + "fps) " +
+ publishProgress("Preview callback got " + frames + " frames (~" +
+ Math.round(((double)frames)/(5.0 * numPasses)) + "fps) " +
baseStatus);
}
try {