Commit cd3a9ec9 authored by Thomas Guillem's avatar Thomas Guillem

MediaPlayer: add GLRenderer

Android Java helper for the libvlc_video_set_opengl_callbacks() function.
parent 17165cd2
......@@ -9,6 +9,7 @@ LOCAL_SRC_FILES += libvlcjni-mediaplayer.c
LOCAL_SRC_FILES += libvlcjni-vlcobject.c
LOCAL_SRC_FILES += libvlcjni-media.c libvlcjni-medialist.c libvlcjni-mediadiscoverer.c libvlcjni-rendererdiscoverer.c
LOCAL_SRC_FILES += libvlcjni-dialog.c
LOCAL_SRC_FILES += libvlcjni-glrenderer.c
LOCAL_SRC_FILES += thumbnailer.c
LOCAL_SRC_FILES += std_logger.c
LOCAL_SRC_FILES += dummy.cpp
......
/*****************************************************************************
* 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;
}
......@@ -22,6 +22,7 @@
#define LIBVLCJNI_VLCOBJECT_H
#include <stdbool.h>
#include <stdlib.h>
#include <jni.h>
#include <vlc/vlc.h>
......
......@@ -185,6 +185,8 @@ int VLCJNI_OnLoad(JavaVM *vm, JNIEnv* env)
"org/videolan/libvlc/RendererDiscoverer$Description", true);
GET_CLASS(fields.Dialog.clazz,
"org/videolan/libvlc/Dialog", true);
GET_CLASS(fields.GLRenderer.clazz,
"org/videolan/libvlc/GLRenderer", true);
GET_ID(GetFieldID,
fields.VLCObject.mInstanceID,
......@@ -196,6 +198,11 @@ int VLCJNI_OnLoad(JavaVM *vm, JNIEnv* env)
fields.MediaPlayer.Equalizer.clazz,
"mInstance", "J");
GET_ID(GetFieldID,
fields.GLRenderer.mInstanceID,
fields.GLRenderer.clazz,
"mInstance", "J");
GET_ID(GetMethodID,
fields.VLCObject.dispatchEventFromNativeID,
fields.VLCObject.clazz,
......@@ -339,6 +346,10 @@ int VLCJNI_OnLoad(JavaVM *vm, JNIEnv* env)
"updateProgressFromNative",
"(Lorg/videolan/libvlc/Dialog;FLjava/lang/String;)V");
jclass pointCLazz;
GET_CLASS(pointCLazz, "android/graphics/Point", false);
GET_ID(GetMethodID, fields.Point.setID, pointCLazz, "set", "(II)V");
#undef GET_CLASS
#undef GET_ID
......
......@@ -114,6 +114,13 @@ struct fields {
jmethodID cancelFromNativeID;
jmethodID updateProgressFromNativeID;
} Dialog;
struct {
jmethodID setID;
} Point;
struct {
jclass clazz;
jfieldID mInstanceID;
} GLRenderer;
};
extern struct fields fields;
......
......@@ -189,6 +189,7 @@ public class AWindow implements IVLCVout {
private OnNewVideoLayoutListener mOnNewVideoLayoutListener = null;
private ArrayList<IVLCVout.Callback> mIVLCVoutCallbacks = new ArrayList<IVLCVout.Callback>();
private final Handler mHandler = new Handler(Looper.getMainLooper());
private boolean mUseGLRenderer = false;
/* synchronized Surfaces accessed by an other thread from JNI */
private final Surface[] mSurfaces;
private long mCallbackNativeHandle = 0;
......@@ -215,6 +216,8 @@ public class AWindow implements IVLCVout {
}
private void ensureInitState() throws IllegalStateException {
if (mUseGLRenderer)
throw new IllegalStateException("Can't set view when using GL Renderer.");
if (mSurfacesState.get() != SURFACE_STATE_INIT)
throw new IllegalStateException("Can't set view when already attached. " +
"Current state: " + mSurfacesState.get() + ", " +
......@@ -351,6 +354,11 @@ public class AWindow implements IVLCVout {
mSurfaceTextureThread.release();
}
@MainThread
protected void useGLRenderer() {
mUseGLRenderer = true;
}
@Override
@MainThread
public boolean areViewsAttached() {
......
/*****************************************************************************
* 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);
}
\ No newline at end of file
......@@ -411,6 +411,8 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
private final AWindow mWindow = new AWindow(mSurfaceListener);
private GLRenderer mGLRenderer = null;
private synchronized void updateAudioOutputDevice(long encodingFlags, String defaultDevice) {
mCanDoPassthrough = encodingFlags != 0;
final String newDeviceId = mAudioDigitalOutputEnabled && mCanDoPassthrough ? "encoded:" + encodingFlags : defaultDevice;
......@@ -573,6 +575,16 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
return mWindow;
}
public GLRenderer enableGLRenderer(int eglContextClientVersion) {
if (mWindow.areViewsAttached())
throw new IllegalArgumentException("can't work with views attached to IVLCVout");
if (mGLRenderer != null)
throw new IllegalArgumentException("GLRenderer already enabled");
mWindow.useGLRenderer();
mGLRenderer = new GLRenderer(eglContextClientVersion, this, mSurfaceListener);
return mGLRenderer;
}
/**
* Set a Media
*
......@@ -630,7 +642,7 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
if (mListenAudioPlug)
registerAudioPlug(true);
mPlayRequested = true;
if (mWindow.areSurfacesWaiting())
if (mWindow.areSurfacesWaiting() || (mGLRenderer != null && !mGLRenderer.isValid()))
return;
}
mPlaying = true;
......@@ -888,7 +900,8 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
*/
public boolean setVideoTrack(int index) {
/* Don't activate a video track is surfaces are not ready */
if (index == -1 || (mWindow.areViewsAttached() && !mWindow.areSurfacesWaiting())) {
if (index == -1 || (mWindow.areViewsAttached() && !mWindow.areSurfacesWaiting())
|| (mGLRenderer != null && mGLRenderer.isValid())) {
return nativeSetVideoTrack(index);
} else
return false;
......@@ -1204,6 +1217,8 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
@Override
protected void onReleaseNative() {
if (mGLRenderer != null)
mGLRenderer.release();
mWindow.detachViews();
registerAudioPlug(false);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment