diff --git a/res/layout-land/videocall.xml b/res/layout-land/videocall.xml index 22bcc2b79..cf2f4ec7b 100644 --- a/res/layout-land/videocall.xml +++ b/res/layout-land/videocall.xml @@ -4,7 +4,7 @@ android:id="@+id/video_frame" android:orientation="vertical" android:layout_height="fill_parent" android:layout_width="fill_parent"> - + diff --git a/res/layout/videocall.xml b/res/layout/videocall.xml index 33da7f149..377b07f04 100644 --- a/res/layout/videocall.xml +++ b/res/layout/videocall.xml @@ -4,7 +4,7 @@ android:id="@+id/video_frame" android:layout_height="fill_parent" android:layout_width="fill_parent"> - + \ No newline at end of file diff --git a/src/org/linphone/GL2JNIView.java b/src/org/linphone/GL2JNIView.java new file mode 100644 index 000000000..ff7ee0c69 --- /dev/null +++ b/src/org/linphone/GL2JNIView.java @@ -0,0 +1,274 @@ +package org.linphone; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; + +import android.content.Context; +import android.graphics.PixelFormat; +import android.opengl.GLSurfaceView; +import android.util.AttributeSet; +import android.util.Log; + +class GL2JNIView extends GLSurfaceView { + private static String TAG = "GL2JNIView"; + private static final boolean DEBUG = false; + + public GL2JNIView(Context context) { + super(context); + init(false, 0, 0); + } + + public GL2JNIView(Context context, AttributeSet att) { + super(context, att); + init(false, 0, 0); + } + + public GL2JNIView(Context context, boolean translucent, int depth, int stencil) { + super(context); + init(translucent, depth, stencil); + } + + private void init(boolean translucent, int depth, int stencil) { + + /* By default, GLSurfaceView() creates a RGB_565 opaque surface. + * If we want a translucent one, we should change the surface's + * format here, using PixelFormat.TRANSLUCENT for GL Surfaces + * is interpreted as any 32-bit surface with alpha by SurfaceFlinger. + */ + if (translucent) { + this.getHolder().setFormat(PixelFormat.TRANSLUCENT); + } + + /* Setup the context factory for 2.0 rendering. + * See ContextFactory class definition below + */ + setEGLContextFactory(new ContextFactory()); + + /* We need to choose an EGLConfig that matches the format of + * our surface exactly. This is going to be done in our + * custom config chooser. See ConfigChooser class definition + * below. + */ + setEGLConfigChooser( translucent ? + new ConfigChooser(8, 8, 8, 8, depth, stencil) : + new ConfigChooser(5, 6, 5, 0, depth, stencil) ); + } + + private static class ContextFactory implements GLSurfaceView.EGLContextFactory { + private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) { + Log.w(TAG, "creating OpenGL ES 2.0 context"); + checkEglError("Before eglCreateContext", egl); + int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; + EGLContext context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list); + checkEglError("After eglCreateContext", egl); + return context; + } + + public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) { + egl.eglDestroyContext(display, context); + } + } + + private static void checkEglError(String prompt, EGL10 egl) { + int error; + while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) { + Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error)); + } + } + + private static class ConfigChooser implements GLSurfaceView.EGLConfigChooser { + + public ConfigChooser(int r, int g, int b, int a, int depth, int stencil) { + mRedSize = r; + mGreenSize = g; + mBlueSize = b; + mAlphaSize = a; + mDepthSize = depth; + mStencilSize = stencil; + } + + /* This EGL config specification is used to specify 2.0 rendering. + * We use a minimum size of 4 bits for red/green/blue, but will + * perform actual matching in chooseConfig() below. + */ + private static int EGL_OPENGL_ES2_BIT = 4; + private static int[] s_configAttribs2 = + { + EGL10.EGL_RED_SIZE, 4, + EGL10.EGL_GREEN_SIZE, 4, + EGL10.EGL_BLUE_SIZE, 4, + EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL10.EGL_NONE + }; + + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + + /* Get the number of minimally matching EGL configurations + */ + int[] num_config = new int[1]; + egl.eglChooseConfig(display, s_configAttribs2, null, 0, num_config); + + int numConfigs = num_config[0]; + + if (numConfigs <= 0) { + throw new IllegalArgumentException("No configs match configSpec"); + } + + /* Allocate then read the array of minimally matching EGL configs + */ + EGLConfig[] configs = new EGLConfig[numConfigs]; + egl.eglChooseConfig(display, s_configAttribs2, configs, numConfigs, num_config); + + if (DEBUG) { + printConfigs(egl, display, configs); + } + /* Now return the "best" one + */ + return chooseConfig(egl, display, configs); + } + + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, + EGLConfig[] configs) { + for(EGLConfig config : configs) { + int d = findConfigAttrib(egl, display, config, + EGL10.EGL_DEPTH_SIZE, 0); + int s = findConfigAttrib(egl, display, config, + EGL10.EGL_STENCIL_SIZE, 0); + + // We need at least mDepthSize and mStencilSize bits + if (d < mDepthSize || s < mStencilSize) + continue; + + // We want an *exact* match for red/green/blue/alpha + int r = findConfigAttrib(egl, display, config, + EGL10.EGL_RED_SIZE, 0); + int g = findConfigAttrib(egl, display, config, + EGL10.EGL_GREEN_SIZE, 0); + int b = findConfigAttrib(egl, display, config, + EGL10.EGL_BLUE_SIZE, 0); + int a = findConfigAttrib(egl, display, config, + EGL10.EGL_ALPHA_SIZE, 0); + + if (r == mRedSize && g == mGreenSize && b == mBlueSize && a == mAlphaSize) + return config; + } + return null; + } + + private int findConfigAttrib(EGL10 egl, EGLDisplay display, + EGLConfig config, int attribute, int defaultValue) { + + if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { + return mValue[0]; + } + return defaultValue; + } + + private void printConfigs(EGL10 egl, EGLDisplay display, + EGLConfig[] configs) { + int numConfigs = configs.length; + Log.w(TAG, String.format("%d configurations", numConfigs)); + for (int i = 0; i < numConfigs; i++) { + Log.w(TAG, String.format("Configuration %d:\n", i)); + printConfig(egl, display, configs[i]); + } + } + + private void printConfig(EGL10 egl, EGLDisplay display, + EGLConfig config) { + int[] attributes = { + EGL10.EGL_BUFFER_SIZE, + EGL10.EGL_ALPHA_SIZE, + EGL10.EGL_BLUE_SIZE, + EGL10.EGL_GREEN_SIZE, + EGL10.EGL_RED_SIZE, + EGL10.EGL_DEPTH_SIZE, + EGL10.EGL_STENCIL_SIZE, + EGL10.EGL_CONFIG_CAVEAT, + EGL10.EGL_CONFIG_ID, + EGL10.EGL_LEVEL, + EGL10.EGL_MAX_PBUFFER_HEIGHT, + EGL10.EGL_MAX_PBUFFER_PIXELS, + EGL10.EGL_MAX_PBUFFER_WIDTH, + EGL10.EGL_NATIVE_RENDERABLE, + EGL10.EGL_NATIVE_VISUAL_ID, + EGL10.EGL_NATIVE_VISUAL_TYPE, + 0x3030, // EGL10.EGL_PRESERVED_RESOURCES, + EGL10.EGL_SAMPLES, + EGL10.EGL_SAMPLE_BUFFERS, + EGL10.EGL_SURFACE_TYPE, + EGL10.EGL_TRANSPARENT_TYPE, + EGL10.EGL_TRANSPARENT_RED_VALUE, + EGL10.EGL_TRANSPARENT_GREEN_VALUE, + EGL10.EGL_TRANSPARENT_BLUE_VALUE, + 0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB, + 0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA, + 0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL, + 0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL, + EGL10.EGL_LUMINANCE_SIZE, + EGL10.EGL_ALPHA_MASK_SIZE, + EGL10.EGL_COLOR_BUFFER_TYPE, + EGL10.EGL_RENDERABLE_TYPE, + 0x3042 // EGL10.EGL_CONFORMANT + }; + String[] names = { + "EGL_BUFFER_SIZE", + "EGL_ALPHA_SIZE", + "EGL_BLUE_SIZE", + "EGL_GREEN_SIZE", + "EGL_RED_SIZE", + "EGL_DEPTH_SIZE", + "EGL_STENCIL_SIZE", + "EGL_CONFIG_CAVEAT", + "EGL_CONFIG_ID", + "EGL_LEVEL", + "EGL_MAX_PBUFFER_HEIGHT", + "EGL_MAX_PBUFFER_PIXELS", + "EGL_MAX_PBUFFER_WIDTH", + "EGL_NATIVE_RENDERABLE", + "EGL_NATIVE_VISUAL_ID", + "EGL_NATIVE_VISUAL_TYPE", + "EGL_PRESERVED_RESOURCES", + "EGL_SAMPLES", + "EGL_SAMPLE_BUFFERS", + "EGL_SURFACE_TYPE", + "EGL_TRANSPARENT_TYPE", + "EGL_TRANSPARENT_RED_VALUE", + "EGL_TRANSPARENT_GREEN_VALUE", + "EGL_TRANSPARENT_BLUE_VALUE", + "EGL_BIND_TO_TEXTURE_RGB", + "EGL_BIND_TO_TEXTURE_RGBA", + "EGL_MIN_SWAP_INTERVAL", + "EGL_MAX_SWAP_INTERVAL", + "EGL_LUMINANCE_SIZE", + "EGL_ALPHA_MASK_SIZE", + "EGL_COLOR_BUFFER_TYPE", + "EGL_RENDERABLE_TYPE", + "EGL_CONFORMANT" + }; + int[] value = new int[1]; + for (int i = 0; i < attributes.length; i++) { + int attribute = attributes[i]; + String name = names[i]; + if ( egl.eglGetConfigAttrib(display, config, attribute, value)) { + Log.w(TAG, String.format(" %s: %d\n", name, value[0])); + } else { + // Log.w(TAG, String.format(" %s: failed\n", name)); + while (egl.eglGetError() != EGL10.EGL_SUCCESS); + } + } + } + + // Subclasses can adjust these values: + protected int mRedSize; + protected int mGreenSize; + protected int mBlueSize; + protected int mAlphaSize; + protected int mDepthSize; + protected int mStencilSize; + private int[] mValue = new int[1]; + } +} \ No newline at end of file diff --git a/src/org/linphone/OpenGLESDisplay.java b/src/org/linphone/OpenGLESDisplay.java new file mode 100644 index 000000000..358667874 --- /dev/null +++ b/src/org/linphone/OpenGLESDisplay.java @@ -0,0 +1,6 @@ +package org.linphone; + +public class OpenGLESDisplay { + public static native void init(int ptr, int width, int height); + public static native void render(int ptr); +} diff --git a/src/org/linphone/core/AndroidVideoWindowImpl.java b/src/org/linphone/core/AndroidVideoWindowImpl.java index 254efb379..2fd27b424 100644 --- a/src/org/linphone/core/AndroidVideoWindowImpl.java +++ b/src/org/linphone/core/AndroidVideoWindowImpl.java @@ -1,8 +1,14 @@ package org.linphone.core; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +import org.linphone.OpenGLESDisplay; + import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Bitmap.Config; +import android.opengl.GLSurfaceView; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; @@ -10,15 +16,18 @@ import android.view.Surface.OutOfResourcesException; import android.view.SurfaceHolder.Callback; public class AndroidVideoWindowImpl { - private Bitmap mBitmap; + private boolean useGLrendering; + private Bitmap mBitmap; private SurfaceView mView; - private Surface mSurface; + private Surface mSurface; private VideoWindowListener mListener; + private Renderer renderer; public static interface VideoWindowListener{ void onSurfaceReady(AndroidVideoWindowImpl vw); void onSurfaceDestroyed(AndroidVideoWindowImpl vw); }; public AndroidVideoWindowImpl(SurfaceView view){ + useGLrendering = (view instanceof GLSurfaceView); mView=view; mBitmap=null; mSurface=null; @@ -27,9 +36,11 @@ public class AndroidVideoWindowImpl { public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Log.i("Surface is being changed."); - synchronized(AndroidVideoWindowImpl.this){ - mBitmap=Bitmap.createBitmap(width,height,Config.RGB_565); - mSurface=holder.getSurface(); + if (!useGLrendering) { + synchronized(AndroidVideoWindowImpl.this){ + mBitmap=Bitmap.createBitmap(width,height,Config.RGB_565); + mSurface=holder.getSurface(); + } } if (mListener!=null) mListener.onSurfaceReady(AndroidVideoWindowImpl.this); Log.w("Video display surface changed"); @@ -40,15 +51,23 @@ public class AndroidVideoWindowImpl { } public void surfaceDestroyed(SurfaceHolder holder) { - synchronized(AndroidVideoWindowImpl.this){ - mSurface=null; - mBitmap=null; + if (!useGLrendering) { + synchronized(AndroidVideoWindowImpl.this){ + mSurface=null; + mBitmap=null; + } } if (mListener!=null) mListener.onSurfaceDestroyed(AndroidVideoWindowImpl.this); Log.d("Video display surface destroyed"); } }); + + if (useGLrendering) { + renderer = new Renderer(); + ((GLSurfaceView)mView).setRenderer(renderer); + ((GLSurfaceView)mView).setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); + } } static final int LANDSCAPE=0; static final int PORTRAIT=1; @@ -57,31 +76,82 @@ public class AndroidVideoWindowImpl { //Log.d("Orientation changed."); } public void setListener(VideoWindowListener l){ - mListener=l; + mListener=l; } public Surface getSurface(){ + if (useGLrendering) + Log.e("View class does not match Video display filter used (you must use a non-GL View)"); return mView.getHolder().getSurface(); } public Bitmap getBitmap(){ + if (useGLrendering) + Log.e("View class does not match Video display filter used (you must use a non-GL View)"); return mBitmap; } - //Called by the mediastreamer2 android display filter + + public void setOpenGLESDisplay(int ptr) { + if (!useGLrendering) + Log.e("View class does not match Video display filter used (you must use a GL View)"); + renderer.setOpenGLESDisplay(ptr); + } + + public void requestRender() { + ((GLSurfaceView)mView).requestRender(); + } + + //Called by the mediastreamer2 android display filter public synchronized void update(){ if (mSurface!=null){ try { - Canvas canvas=mSurface.lockCanvas(null); + Canvas canvas=mSurface.lockCanvas(null); canvas.drawBitmap(mBitmap, 0, 0, null); mSurface.unlockCanvasAndPost(canvas); - + } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (OutOfResourcesException e) { // TODO Auto-generated catch block e.printStackTrace(); - } - } + } + } } + + private static class Renderer implements GLSurfaceView.Renderer { + int ptr; + boolean initPending; + int width, height; + + public Renderer() { + ptr = 0; + initPending = false; + } + + public void setOpenGLESDisplay(int ptr) { + this.ptr = ptr; + } + + public void onDrawFrame(GL10 gl) { + if (ptr == 0) + return; + if (initPending) { + OpenGLESDisplay.init(ptr, width, height); + initPending = false; + } + OpenGLESDisplay.render(ptr); + } + + public void onSurfaceChanged(GL10 gl, int width, int height) { + /* delay init until ptr is set */ + this.width = width; + this.height = height; + initPending = true; + } + + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + + } + } } diff --git a/submodules/linphone b/submodules/linphone index 612f0cf32..f5f8a1557 160000 --- a/submodules/linphone +++ b/submodules/linphone @@ -1 +1 @@ -Subproject commit 612f0cf326f4d2c3abdb5cd6d7967b918a2ef7ac +Subproject commit f5f8a155701bc1c36483c2f1d4433d41a61bb396