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 {