Browse Source
Android Java helper for the libvlc_video_set_opengl_callbacks() function.4.0-glrenderer
8 changed files with 602 additions and 2 deletions
@ -0,0 +1,442 @@ |
|||
/*****************************************************************************
|
|||
* libvlcjni-glrenderer.c |
|||
***************************************************************************** |
|||
* Copyright © 2018 VLC authors, VideoLAN and VideoLabs |
|||
* |
|||
* This program is free software; you can redistribute it and/or modify it |
|||
* under the terms of the GNU Lesser General Public License as published by |
|||
* the Free Software Foundation; either version 2.1 of the License, or |
|||
* (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Lesser General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Lesser General Public License |
|||
* along with this program; if not, write to the Free Software Foundation, |
|||
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. |
|||
*****************************************************************************/ |
|||
|
|||
#include <assert.h> |
|||
#include <pthread.h> |
|||
|
|||
#include <GLES2/gl2.h> |
|||
#include <EGL/egl.h> |
|||
|
|||
#include "libvlcjni-vlcobject.h" |
|||
|
|||
#define THREAD_NAME "GLRenderer" |
|||
extern JNIEnv *jni_get_env(const char *name); |
|||
|
|||
struct glrenderer |
|||
{ |
|||
pthread_mutex_t lock; |
|||
|
|||
EGLint egl_version; |
|||
|
|||
EGLDisplay display; |
|||
EGLSurface surface; |
|||
EGLContext context; |
|||
|
|||
unsigned width; |
|||
unsigned height; |
|||
GLuint texs[3]; |
|||
GLuint fbos[3]; |
|||
size_t idx_render; |
|||
size_t idx_swap; |
|||
size_t idx_display; |
|||
bool updated; |
|||
bool destroy_unused_egl; |
|||
}; |
|||
|
|||
static struct glrenderer * |
|||
GLRenderer_getInstance(JNIEnv *env, jobject thiz) |
|||
{ |
|||
struct glrenderer *glr = (struct glrenderer*)(intptr_t) |
|||
(*env)->GetLongField(env, thiz, fields.GLRenderer.mInstanceID); |
|||
if (!glr) |
|||
throw_Exception(env, VLCJNI_EX_ILLEGAL_STATE, |
|||
"can't get GLRenderer instance"); |
|||
return glr; |
|||
} |
|||
|
|||
|
|||
static void |
|||
GLRenderer_setInstance(JNIEnv *env, jobject thiz, struct glrenderer *glr) |
|||
{ |
|||
(*env)->SetLongField(env, thiz, fields.GLRenderer.mInstanceID, |
|||
(jlong)(intptr_t)glr); |
|||
} |
|||
|
|||
static inline void swap(size_t *a, size_t *b) |
|||
{ |
|||
size_t tmp = *a; |
|||
*a = *b; |
|||
*b = tmp; |
|||
} |
|||
|
|||
static void |
|||
glrenderer_destroy_egl(struct glrenderer *glr) |
|||
{ |
|||
if (glr->display != EGL_NO_DISPLAY) |
|||
{ |
|||
if (glr->context != EGL_NO_CONTEXT) |
|||
{ |
|||
eglDestroyContext(glr->display, glr->context); |
|||
glr->context = EGL_NO_CONTEXT; |
|||
} |
|||
if (glr->surface != EGL_NO_SURFACE) |
|||
{ |
|||
eglDestroySurface(glr->display, glr->surface); |
|||
glr->surface = EGL_NO_SURFACE; |
|||
} |
|||
glr->display = EGL_NO_DISPLAY; |
|||
} |
|||
glr->destroy_unused_egl = false; |
|||
} |
|||
|
|||
static bool |
|||
gl_setup(void* opaque) |
|||
{ |
|||
struct glrenderer *glr = opaque; |
|||
|
|||
pthread_mutex_lock(&glr->lock); |
|||
|
|||
glr->width = glr->height = 0; |
|||
glr->updated = false; |
|||
glr->destroy_unused_egl = false; |
|||
|
|||
pthread_mutex_unlock(&glr->lock); |
|||
return true; |
|||
} |
|||
|
|||
static void |
|||
gl_cleanup_locked(struct glrenderer *glr) |
|||
{ |
|||
if (glr->width == 0 && glr->height == 0) |
|||
return; |
|||
|
|||
glDeleteTextures(3, glr->texs); |
|||
glDeleteFramebuffers(3, glr->fbos); |
|||
glr->width = glr->height = 0; |
|||
} |
|||
|
|||
static void |
|||
gl_resize_locked(struct glrenderer *glr, unsigned width, unsigned height) |
|||
{ |
|||
if (width != glr->width || height != glr->height) |
|||
gl_cleanup_locked(glr); |
|||
|
|||
glGenTextures(3, glr->texs); |
|||
glGenFramebuffers(3, glr->fbos); |
|||
|
|||
for (int i = 0; i < 3; i++) |
|||
{ |
|||
glBindTexture(GL_TEXTURE_2D, glr->texs[i]); |
|||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); |
|||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
|||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
|||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
|||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
|||
|
|||
glBindFramebuffer(GL_FRAMEBUFFER, glr->fbos[i]); |
|||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, glr->texs[i], 0); |
|||
} |
|||
glBindTexture(GL_TEXTURE_2D, 0); |
|||
|
|||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); |
|||
|
|||
if (status != GL_FRAMEBUFFER_COMPLETE) |
|||
return; |
|||
|
|||
glr->width = width; |
|||
glr->height = height; |
|||
|
|||
glBindFramebuffer(GL_FRAMEBUFFER, glr->fbos[glr->idx_render]); |
|||
} |
|||
|
|||
static void |
|||
gl_cleanup(void* opaque) |
|||
{ |
|||
struct glrenderer *glr = opaque; |
|||
|
|||
pthread_mutex_lock(&glr->lock); |
|||
|
|||
if (glr->display) |
|||
{ |
|||
EGLBoolean ret = eglMakeCurrent(glr->display, glr->surface, |
|||
glr->surface, glr->context); |
|||
if (ret) |
|||
{ |
|||
gl_cleanup_locked(glr); |
|||
eglMakeCurrent(glr->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); |
|||
} |
|||
} |
|||
|
|||
if (glr->destroy_unused_egl) |
|||
glrenderer_destroy_egl(glr); |
|||
|
|||
glr->width = glr->height = 0; |
|||
|
|||
pthread_mutex_unlock(&glr->lock); |
|||
} |
|||
|
|||
static void |
|||
gl_resize(void* opaque, unsigned width, unsigned height) |
|||
{ |
|||
struct glrenderer *glr = opaque; |
|||
|
|||
pthread_mutex_lock(&glr->lock); |
|||
gl_resize_locked(glr, width, height); |
|||
pthread_mutex_unlock(&glr->lock); |
|||
} |
|||
|
|||
static void |
|||
gl_swap(void* opaque) |
|||
{ |
|||
struct glrenderer *glr = opaque; |
|||
|
|||
pthread_mutex_lock(&glr->lock); |
|||
glr->updated = true; |
|||
swap(&glr->idx_swap, &glr->idx_render); |
|||
glBindFramebuffer(GL_FRAMEBUFFER, glr->fbos[glr->idx_render]); |
|||
pthread_mutex_unlock(&glr->lock); |
|||
} |
|||
|
|||
static bool |
|||
gl_makeCurrent(void* opaque, bool enter) |
|||
{ |
|||
struct glrenderer *glr = opaque; |
|||
|
|||
pthread_mutex_lock(&glr->lock); |
|||
|
|||
EGLBoolean ret; |
|||
if (!glr->display) |
|||
ret = EGL_FALSE; |
|||
else if (enter) |
|||
ret = eglMakeCurrent(glr->display, glr->surface, glr->surface, glr->context); |
|||
else |
|||
ret = eglMakeCurrent(glr->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); |
|||
|
|||
pthread_mutex_unlock(&glr->lock); |
|||
return ret; |
|||
} |
|||
|
|||
static void* |
|||
gl_getProcAddress(void* opaque, const char* fct_name) |
|||
{ |
|||
(void) opaque; |
|||
return eglGetProcAddress(fct_name); |
|||
} |
|||
|
|||
void |
|||
Java_org_videolan_libvlc_GLRenderer_nativeInit(JNIEnv *env, jobject thiz, |
|||
jobject thizmp, |
|||
jint egl_version) |
|||
{ |
|||
if (egl_version != 2 && egl_version != 3) |
|||
{ |
|||
throw_Exception(env, VLCJNI_EX_ILLEGAL_ARGUMENT, |
|||
"Invalid EGLVersion (should be 2 or 3)"); |
|||
} |
|||
|
|||
vlcjni_object *mpobj = VLCJniObject_getInstance(env, thizmp); |
|||
if (!mpobj) |
|||
return; |
|||
if (!mpobj->u.p_mp) |
|||
{ |
|||
throw_Exception(env, VLCJNI_EX_ILLEGAL_STATE, |
|||
"MediaPlayer instance invalid"); |
|||
return; |
|||
} |
|||
|
|||
struct glrenderer *glr = malloc(sizeof(*glr)); |
|||
if (!glr) |
|||
{ |
|||
throw_Exception(env, VLCJNI_EX_OUT_OF_MEMORY, "GLRenderer"); |
|||
return; |
|||
} |
|||
|
|||
glr->egl_version = egl_version; |
|||
|
|||
glr->display = EGL_NO_DISPLAY; |
|||
glr->surface = EGL_NO_SURFACE; |
|||
glr->context = EGL_NO_CONTEXT; |
|||
|
|||
glr->width = glr->height = 0; |
|||
|
|||
glr->idx_render = 0; |
|||
glr->idx_swap = 1; |
|||
glr->idx_display = 2; |
|||
|
|||
glr->updated = false; |
|||
|
|||
pthread_mutex_init(&glr->lock, NULL); |
|||
|
|||
libvlc_video_set_opengl_callbacks(mpobj->u.p_mp, |
|||
libvlc_gl_engine_gles2, |
|||
gl_setup, |
|||
gl_cleanup, |
|||
gl_resize, |
|||
gl_swap, |
|||
gl_makeCurrent, |
|||
gl_getProcAddress, |
|||
glr); |
|||
|
|||
GLRenderer_setInstance(env, thiz, glr); |
|||
} |
|||
|
|||
void |
|||
Java_org_videolan_libvlc_GLRenderer_nativeRelease(JNIEnv *env, jobject thiz) |
|||
{ |
|||
struct glrenderer *glr = GLRenderer_getInstance(env, thiz); |
|||
if (!glr) |
|||
return; |
|||
|
|||
pthread_mutex_destroy(&glr->lock); |
|||
|
|||
glrenderer_destroy_egl(glr); |
|||
free(glr); |
|||
|
|||
GLRenderer_setInstance(env, thiz, NULL); |
|||
} |
|||
|
|||
void |
|||
Java_org_videolan_libvlc_GLRenderer_nativeOnSurfaceCreated(JNIEnv *env, jobject thiz) |
|||
{ |
|||
struct glrenderer *glr = GLRenderer_getInstance(env, thiz); |
|||
if (!glr) |
|||
return; |
|||
|
|||
pthread_mutex_lock(&glr->lock); |
|||
|
|||
glrenderer_destroy_egl(glr); |
|||
|
|||
const EGLint config_attr[] = { |
|||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT, |
|||
EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT, |
|||
EGL_RED_SIZE, 8, |
|||
EGL_GREEN_SIZE, 8, |
|||
EGL_BLUE_SIZE, 8, |
|||
EGL_ALPHA_SIZE, 8, |
|||
EGL_NONE |
|||
}; |
|||
|
|||
const EGLint surface_attr[] = { |
|||
EGL_WIDTH, 2, |
|||
EGL_HEIGHT, 2, |
|||
EGL_NONE |
|||
}; |
|||
|
|||
const EGLint ctx_attr[] = { |
|||
EGL_CONTEXT_CLIENT_VERSION, glr->egl_version, |
|||
EGL_NONE |
|||
}; |
|||
|
|||
EGLConfig config; |
|||
EGLint num_configs; |
|||
|
|||
glr->display = eglGetDisplay(EGL_DEFAULT_DISPLAY); |
|||
if (glr->display == EGL_NO_DISPLAY || eglGetError() != EGL_SUCCESS) |
|||
{ |
|||
throw_Exception(env, VLCJNI_EX_RUNTIME, |
|||
"eglGetCurrentDisplay error: %x", eglGetError()); |
|||
goto error; |
|||
} |
|||
|
|||
if (!eglInitialize(glr->display, NULL, NULL)) |
|||
{ |
|||
throw_Exception(env, VLCJNI_EX_RUNTIME, |
|||
"eglInitialize() error: %x", eglGetError()); |
|||
goto error; |
|||
} |
|||
|
|||
if (!eglChooseConfig(glr->display, config_attr, &config, 1, &num_configs) |
|||
|| eglGetError() != EGL_SUCCESS) |
|||
{ |
|||
throw_Exception(env, VLCJNI_EX_RUNTIME, |
|||
"eglGetConfigAttrib() error: %x", eglGetError()); |
|||
goto error; |
|||
} |
|||
|
|||
EGLContext current_ctx = eglGetCurrentContext(); |
|||
if (eglGetError() != EGL_SUCCESS) |
|||
{ |
|||
throw_Exception(env, VLCJNI_EX_RUNTIME, |
|||
"eglGetCurrentContext() error %x", eglGetError()); |
|||
goto error; |
|||
} |
|||
|
|||
glr->surface = eglCreatePbufferSurface(glr->display, config, surface_attr); |
|||
if (glr->surface == EGL_NO_SURFACE || eglGetError() != EGL_SUCCESS) |
|||
{ |
|||
throw_Exception(env, VLCJNI_EX_RUNTIME, |
|||
"eglCreatePbufferSurface() error %x", eglGetError()); |
|||
goto error; |
|||
} |
|||
|
|||
|
|||
glr->context = eglCreateContext(glr->display, config, current_ctx, ctx_attr); |
|||
if (glr->context == EGL_NO_CONTEXT || eglGetError() != EGL_SUCCESS) |
|||
{ |
|||
throw_Exception(env, VLCJNI_EX_RUNTIME, |
|||
"eglCreateContext() error: %x", eglGetError()); |
|||
goto error; |
|||
} |
|||
|
|||
pthread_mutex_unlock(&glr->lock); |
|||
|
|||
return; |
|||
error: |
|||
glrenderer_destroy_egl(glr); |
|||
pthread_mutex_unlock(&glr->lock); |
|||
} |
|||
|
|||
void |
|||
Java_org_videolan_libvlc_GLRenderer_nativeOnSurfaceDestroyed(JNIEnv *env, jobject thiz) |
|||
{ |
|||
struct glrenderer *glr = GLRenderer_getInstance(env, thiz); |
|||
if (!glr) |
|||
return; |
|||
|
|||
pthread_mutex_lock(&glr->lock); |
|||
|
|||
if (glr->width == 0 && glr->height == 0) |
|||
glrenderer_destroy_egl(glr); |
|||
else |
|||
glr->destroy_unused_egl = true; |
|||
|
|||
pthread_mutex_unlock(&glr->lock); |
|||
} |
|||
|
|||
jint |
|||
Java_org_videolan_libvlc_GLRenderer_nativeGetVideoTexture(JNIEnv *env, jobject thiz, |
|||
jobject point) |
|||
{ |
|||
struct glrenderer *glr = GLRenderer_getInstance(env, thiz); |
|||
if (!glr) |
|||
return 0; |
|||
|
|||
pthread_mutex_lock(&glr->lock); |
|||
|
|||
jint tex_id; |
|||
if (glr->width == 0 || glr->height == 0) |
|||
tex_id = 0; |
|||
else |
|||
{ |
|||
if (glr->updated) |
|||
{ |
|||
swap(&glr->idx_swap, &glr->idx_display); |
|||
glr->updated = false; |
|||
} |
|||
tex_id = glr->texs[glr->idx_display]; |
|||
if (point != NULL) |
|||
(*env)->CallVoidMethod(env, point, fields.Point.setID, |
|||
glr->width, glr->height); |
|||
} |
|||
|
|||
pthread_mutex_unlock(&glr->lock); |
|||
|
|||
return tex_id; |
|||
} |
|||
@ -0,0 +1,115 @@ |
|||
/***************************************************************************** |
|||
* GLRenderer.java |
|||
***************************************************************************** |
|||
* Copyright © 2018 VLC authors and VideoLAN |
|||
** |
|||
* This program is free software; you can redistribute it and/or modify it |
|||
* under the terms of the GNU Lesser General Public License as published by |
|||
* the Free Software Foundation; either version 2.1 of the License, or |
|||
* (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Lesser General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Lesser General Public License |
|||
* along with this program; if not, write to the Free Software Foundation, |
|||
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. |
|||
*****************************************************************************/ |
|||
|
|||
package org.videolan.libvlc; |
|||
|
|||
import android.graphics.Point; |
|||
|
|||
import javax.microedition.khronos.egl.EGLConfig; |
|||
import javax.microedition.khronos.opengles.GL10; |
|||
|
|||
public class GLRenderer { |
|||
|
|||
private MediaPlayer.SurfaceListener mListener; |
|||
private int mLastTextureId = 0; |
|||
private boolean mFrameUpdated = false; |
|||
private boolean mSurfaceCreated = false; |
|||
|
|||
GLRenderer(int eglContextClientVersion, MediaPlayer mp, MediaPlayer.SurfaceListener listener) { |
|||
if (listener == null) |
|||
throw new IllegalArgumentException("listener can't be null"); |
|||
mListener = listener; |
|||
nativeInit(mp, eglContextClientVersion); |
|||
} |
|||
|
|||
void release() { |
|||
nativeRelease(); |
|||
} |
|||
|
|||
@SuppressWarnings("unused") /* Used from JNI */ |
|||
private long mInstance = 0; |
|||
|
|||
synchronized boolean isValid() { |
|||
return mSurfaceCreated; |
|||
} |
|||
|
|||
/** |
|||
* This method need to be called when a new EGL Context is created (from the same thread). |
|||
* |
|||
* Example: call this method from |
|||
* {@link android.opengl.GLSurfaceView.Renderer#onSurfaceCreated(GL10, EGLConfig)}. |
|||
*/ |
|||
synchronized public void onSurfaceCreated() { |
|||
nativeOnSurfaceCreated(); |
|||
if (!mSurfaceCreated) { |
|||
mSurfaceCreated = true; |
|||
mListener.onSurfaceCreated(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* This method need to be called when the EGL Context is destroyed (from any thread). |
|||
* |
|||
* Example: call this method from {@link android.opengl.GLSurfaceView.Renderer#onPause()}. |
|||
*/ |
|||
synchronized public void onSurfaceDestroyed() { |
|||
if (mSurfaceCreated) { |
|||
mSurfaceCreated = false; |
|||
mListener.onSurfaceDestroyed(); |
|||
nativeOnSurfaceDestroyed(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Get the current GL texture ID. |
|||
* |
|||
* @param videoSize Used to get the size of the texture (can be null) |
|||
* @return a valid GL_TEXTURE_2D id or 0 if no video is playing. |
|||
*/ |
|||
public int getVideoTexture(Point videoSize) { |
|||
int newTextureId = nativeGetVideoTexture(videoSize); |
|||
if (newTextureId != mLastTextureId) { |
|||
if (newTextureId != 0) |
|||
mFrameUpdated = true; |
|||
} |
|||
else { |
|||
mLastTextureId = newTextureId; |
|||
mFrameUpdated = false; |
|||
} |
|||
return newTextureId; |
|||
} |
|||
|
|||
/** |
|||
* Check if the video frame changed |
|||
* |
|||
* This method need to be called after {@link GLRenderer#getVideoTexture} |
|||
* |
|||
* @return true of the video frame changed |
|||
*/ |
|||
public boolean isVideoFrameUpdated() { |
|||
return mFrameUpdated; |
|||
} |
|||
|
|||
private native void nativeInit(MediaPlayer mp, int eglContextClientVersion); |
|||
private native void nativeRelease(); |
|||
private native void nativeOnSurfaceCreated(); |
|||
private native void nativeOnSurfaceDestroyed(); |
|||
private native int nativeGetVideoTexture(Point point); |
|||
} |
|||
Loading…
Reference in new issue