You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1210 lines
38 KiB

/*****************************************************************************
* utils.c: shared code between Android vout modules.
*****************************************************************************
* Copyright (C) 2014-2015 VLC authors and VideoLAN
*
* Authors: Felix Abecassis <felix.abecassis@gmail.com>
* Thomas Guillem <thomas@gllm.fr>
*
* 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.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "utils.h"
#include "env.h"
#include <vlc_threads.h>
#include <dlfcn.h>
#include <jni.h>
#include <pthread.h>
#include <assert.h>
#include <EGL/egl.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
typedef void (*ptr_ASurfaceTexture_getTransformMatrix)
(ASurfaceTexture *st, float mtx[16]);
typedef ASurfaceTexture* (*ptr_ASurfaceTexture_fromSurfaceTexture)
(JNIEnv* env, jobject surfacetexture);
typedef ANativeWindow* (*ptr_ASurfaceTexture_acquireANativeWindow)
(ASurfaceTexture *st);
typedef jobject (*ptr_ANativeWindow_toSurface)
(JNIEnv *env, ANativeWindow *window);
typedef int (*ptr_ASurfaceTexture_attachToGLContext)
(ASurfaceTexture *st, uint32_t texName);
typedef int (*ptr_ASurfaceTexture_updateTexImage)(ASurfaceTexture* st);
typedef int (*ptr_ASurfaceTexture_detachFromGLContext)(ASurfaceTexture *st);
typedef void (*ptr_ASurfaceTexture_release)(ASurfaceTexture *st);
/*
* Android SurfaceTexture handle
*/
struct vlc_asurfacetexture_priv {
struct vlc_asurfacetexture surface;
/* Underlying SurfaceTexture objects (JNI and NDK)*/
jobject jtexture;
ASurfaceTexture *texture;
/* Android API are loaded into an AWindowHandler instance. */
struct AWindowHandler *awh;
};
struct ASurfaceTextureAPI
{
float transMat[16];
jobject surfacetexture;
ASurfaceTexture *p_ast;
ptr_ASurfaceTexture_updateTexImage pf_updateTexImage;
ptr_ASurfaceTexture_fromSurfaceTexture pf_astFromst;
ptr_ASurfaceTexture_attachToGLContext pf_attachToGL;
ptr_ASurfaceTexture_detachFromGLContext pf_detachFromGL;
ptr_ASurfaceTexture_getTransformMatrix pf_getTransMatrix;
ptr_ASurfaceTexture_release pf_releaseAst;
ptr_ASurfaceTexture_acquireANativeWindow pf_acquireAnw;
ptr_ANativeWindow_toSurface pf_anwToSurface;
};
struct vlc_android_jfields
{
struct {
jclass clazz;
jmethodID getVideoSurface;
jmethodID getSubtitlesSurface;
jmethodID registerNative;
jmethodID unregisterNative;
jmethodID setVideoLayout;
} AWindow;
struct {
jclass clazz;
jmethodID init_i;
jmethodID init_iz;
jmethodID init_z;
jmethodID updateTexImage;
jmethodID release;
jmethodID releaseTexImage;
jmethodID getTransformMatrix;
jmethodID detachFromGLContext;
jmethodID attachToGLContext;
} SurfaceTexture;
struct {
jclass clazz;
jmethodID init_st;
} Surface;
};
struct AWindowHandler
{
JavaVM *p_jvm;
jobject jobj;
vlc_window_t *wnd;
struct vlc_android_jfields jfields;
struct {
jobject jsurface;
ANativeWindow *p_anw;
} views[AWindow_Max];
void *p_anw_dl;
struct ASurfaceTextureAPI ndk_ast_api;
bool b_has_ndk_ast_api;
struct {
awh_events_t cb;
} event;
int capabilities;
struct {
jfloatArray jtransform_mtx_array;
jfloat *jtransform_mtx;
} stex;
struct aimage_reader_api ndk_air_api;
void *ndk_air_lib;
void *ndk_sync_lib;
bool b_has_ndk_air_api;
struct asurface_control_api ndk_asc_api;
bool b_has_ndk_asc_api;
};
#define JNI_CALL(what, obj, method, ...) \
(*p_env)->what(p_env, obj, p_awh->jfields.method, ##__VA_ARGS__)
#define JNI_ANWCALL(what, method, ...) \
(*p_env)->what(p_env, p_awh->jobj, p_awh->jfields.AWindow.method, ##__VA_ARGS__)
#define JNI_STEXCALL(what, method, ...) \
(*p_env)->what(p_env, p_awh->jobj, p_awh->jfields.AWindow.method, ##__VA_ARGS__)
static int
NDKSurfaceTexture_attachToGLContext(
struct vlc_asurfacetexture *surface,
uint32_t texName)
{
struct vlc_asurfacetexture_priv *handle =
container_of(surface, struct vlc_asurfacetexture_priv, surface);
return handle->awh->ndk_ast_api.pf_attachToGL(handle->texture, texName);
}
static void
NDKSurfaceTexture_detachFromGLContext(
struct vlc_asurfacetexture *surface)
{
struct vlc_asurfacetexture_priv *handle =
container_of(surface, struct vlc_asurfacetexture_priv, surface);
handle->awh->ndk_ast_api.pf_detachFromGL(handle->texture);
}
static int
NDKSurfaceTexture_updateTexImage(
struct vlc_asurfacetexture *surface,
const float **pp_transform_mtx)
{
struct vlc_asurfacetexture_priv *handle =
container_of(surface, struct vlc_asurfacetexture_priv, surface);
/* ASurfaceTexture_updateTexImage can fail, for example if calling it
* before having produced a new image. */
if (handle->awh->ndk_ast_api.pf_updateTexImage(handle->texture))
return VLC_EGENERIC;
handle->awh->ndk_ast_api.pf_getTransMatrix(handle->texture,
handle->awh->ndk_ast_api.transMat);
*pp_transform_mtx = handle->awh->ndk_ast_api.transMat;
return VLC_SUCCESS;
}
static void NDKSurfaceTexture_destroy(
struct vlc_asurfacetexture *surface)
{
struct vlc_asurfacetexture_priv *handle =
container_of(surface, struct vlc_asurfacetexture_priv, surface);
JNIEnv *p_env = android_getEnvCommon(NULL, handle->awh->p_jvm, "SurfaceTexture");
if (!p_env)
return;
(*p_env)->CallVoidMethod(p_env, handle->jtexture,
handle->awh->jfields.SurfaceTexture.release);
if (handle->surface.window)
ANativeWindow_release(handle->surface.window);
if (handle->surface.jsurface)
(*p_env)->DeleteGlobalRef(p_env, handle->surface.jsurface);
handle->awh->ndk_ast_api.pf_releaseAst(handle->texture);
(*p_env)->DeleteGlobalRef(p_env, handle->jtexture);
free(handle);
}
static void
JNISurfaceTexture_releaseTexImage(struct vlc_asurfacetexture *surface);
static const struct vlc_asurfacetexture_operations NDKSurfaceAPI =
{
.attach_to_gl_context = NDKSurfaceTexture_attachToGLContext,
.update_tex_image = NDKSurfaceTexture_updateTexImage,
.detach_from_gl_context = NDKSurfaceTexture_detachFromGLContext,
.release_tex_image = JNISurfaceTexture_releaseTexImage,
.destroy = NDKSurfaceTexture_destroy,
};
static int
JNISurfaceTexture_attachToGLContext(
struct vlc_asurfacetexture *surface,
uint32_t tex_name)
{
struct vlc_asurfacetexture_priv *handle =
container_of(surface, struct vlc_asurfacetexture_priv, surface);
JNIEnv *p_env = android_getEnvCommon(NULL, handle->awh->p_jvm, "SurfaceTexture");
if (!p_env)
return VLC_EGENERIC;
(*p_env)->CallVoidMethod(p_env, handle->jtexture,
handle->awh->jfields.SurfaceTexture.attachToGLContext,
tex_name);
return VLC_SUCCESS;
}
static void
JNISurfaceTexture_detachFromGLContext(
struct vlc_asurfacetexture *surface)
{
struct vlc_asurfacetexture_priv *handle =
container_of(surface, struct vlc_asurfacetexture_priv, surface);
AWindowHandler *p_awh = handle->awh;
JNIEnv *p_env = android_getEnvCommon(NULL, p_awh->p_jvm, "SurfaceTexture");
if (!p_env)
return;
(*p_env)->CallVoidMethod(p_env, handle->jtexture,
handle->awh->jfields.SurfaceTexture.detachFromGLContext);
if (handle->awh->stex.jtransform_mtx != NULL)
{
(*p_env)->ReleaseFloatArrayElements(p_env, handle->awh->stex.jtransform_mtx_array,
handle->awh->stex.jtransform_mtx,
JNI_ABORT);
handle->awh->stex.jtransform_mtx = NULL;
}
}
static int
JNISurfaceTexture_updateTexImage(
struct vlc_asurfacetexture *surface,
const float **pp_transform_mtx)
{
struct vlc_asurfacetexture_priv *handle =
container_of(surface, struct vlc_asurfacetexture_priv, surface);
AWindowHandler *p_awh = handle->awh;
JNIEnv *p_env = android_getEnvCommon(NULL, p_awh->p_jvm, "SurfaceTexture");
if (!p_env)
return VLC_EGENERIC;
if (handle->awh->stex.jtransform_mtx != NULL)
(*p_env)->ReleaseFloatArrayElements(p_env, handle->awh->stex.jtransform_mtx_array,
handle->awh->stex.jtransform_mtx,
JNI_ABORT);
(*p_env)->CallVoidMethod(p_env, handle->jtexture,
handle->awh->jfields.SurfaceTexture.updateTexImage);
(*p_env)->CallVoidMethod(p_env, handle->jtexture,
handle->awh->jfields.SurfaceTexture.getTransformMatrix,
handle->awh->stex.jtransform_mtx_array);
handle->awh->stex.jtransform_mtx = (*p_env)->GetFloatArrayElements(p_env,
handle->awh->stex.jtransform_mtx_array, NULL);
*pp_transform_mtx = handle->awh->stex.jtransform_mtx;
return VLC_SUCCESS;
}
static void
JNISurfaceTexture_releaseTexImage(
struct vlc_asurfacetexture *surface)
{
struct vlc_asurfacetexture_priv *handle =
container_of(surface, struct vlc_asurfacetexture_priv, surface);
JNIEnv *p_env = android_getEnvCommon(NULL, handle->awh->p_jvm, "SurfaceTexture");
if (!p_env)
return;
(*p_env)->CallVoidMethod(p_env, handle->jtexture,
handle->awh->jfields.SurfaceTexture.releaseTexImage);
}
static void JNISurfaceTexture_destroy(
struct vlc_asurfacetexture *surface)
{
struct vlc_asurfacetexture_priv *handle =
container_of(surface, struct vlc_asurfacetexture_priv, surface);
JNIEnv *p_env = android_getEnvCommon(NULL, handle->awh->p_jvm, "SurfaceTexture");
if (!p_env)
return;
(*p_env)->CallVoidMethod(p_env, handle->jtexture,
handle->awh->jfields.SurfaceTexture.release);
if (handle->surface.window)
ANativeWindow_release(handle->surface.window);
if (handle->surface.jsurface)
(*p_env)->DeleteGlobalRef(p_env, handle->surface.jsurface);
free(handle);
}
static const struct vlc_asurfacetexture_operations JNISurfaceAPI =
{
.attach_to_gl_context = JNISurfaceTexture_attachToGLContext,
.update_tex_image = JNISurfaceTexture_updateTexImage,
.detach_from_gl_context = JNISurfaceTexture_detachFromGLContext,
.release_tex_image = JNISurfaceTexture_releaseTexImage,
.destroy = JNISurfaceTexture_destroy,
};
static int
LoadNDKSurfaceTextureAPI(AWindowHandler *p_awh, void *p_library)
{
p_awh->ndk_ast_api.pf_astFromst = (ptr_ASurfaceTexture_fromSurfaceTexture)
dlsym(p_library, "ASurfaceTexture_fromSurfaceTexture");
if (p_awh->ndk_ast_api.pf_astFromst == NULL) return VLC_EGENERIC;
p_awh->ndk_ast_api.pf_updateTexImage = (ptr_ASurfaceTexture_updateTexImage)
dlsym(p_library, "ASurfaceTexture_updateTexImage");
if (p_awh->ndk_ast_api.pf_updateTexImage == NULL) return VLC_EGENERIC;
p_awh->ndk_ast_api.pf_attachToGL = (ptr_ASurfaceTexture_attachToGLContext)
dlsym(p_library, "ASurfaceTexture_attachToGLContext");
if (p_awh->ndk_ast_api.pf_attachToGL == NULL) return VLC_EGENERIC;
p_awh->ndk_ast_api.pf_detachFromGL = (ptr_ASurfaceTexture_detachFromGLContext)
dlsym(p_library, "ASurfaceTexture_detachFromGLContext");
if (p_awh->ndk_ast_api.pf_detachFromGL == NULL) return VLC_EGENERIC;
p_awh->ndk_ast_api.pf_getTransMatrix = (ptr_ASurfaceTexture_getTransformMatrix)
dlsym(p_library, "ASurfaceTexture_getTransformMatrix");
if (p_awh->ndk_ast_api.pf_getTransMatrix == NULL) return VLC_EGENERIC;
p_awh->ndk_ast_api.pf_releaseAst = (ptr_ASurfaceTexture_release)
dlsym(p_library, "ASurfaceTexture_release");
if (p_awh->ndk_ast_api.pf_releaseAst == NULL) return VLC_EGENERIC;
p_awh->ndk_ast_api.pf_acquireAnw = (ptr_ASurfaceTexture_acquireANativeWindow)
dlsym(p_library, "ASurfaceTexture_acquireANativeWindow");
if (p_awh->ndk_ast_api.pf_acquireAnw == NULL) return VLC_EGENERIC;
p_awh->ndk_ast_api.pf_anwToSurface = (ptr_ANativeWindow_toSurface)
dlsym(p_library, "ANativeWindow_toSurface");
if (p_awh->ndk_ast_api.pf_anwToSurface == NULL) return VLC_EGENERIC;
return VLC_SUCCESS;
}
static int
LoadAimageReaderAPI(AWindowHandler *p_awh, void *p_android_lib)
{
/* AHardwareBuffer functions are in libandroid.so, API 31+ */
#define LOAD(object, name) \
p_awh->ndk_air_api.object.name = dlsym(p_android_lib, #object "_" #name); \
if (p_awh->ndk_air_api.object.name == NULL) return VLC_EGENERIC
LOAD(AHardwareBuffer, getId);
LOAD(AHardwareBuffer, describe);
#undef LOAD
void *p_library = dlopen("libmediandk.so", RTLD_NOW);
if (p_library == NULL)
return VLC_EGENERIC;
#define LOAD(object, name) \
p_awh->ndk_air_api.object.name = dlsym(p_library, #object "_" #name); \
if (p_awh->ndk_air_api.object.name == NULL) goto error
/* API 29+ */
LOAD(AImageReader, newWithUsage);
LOAD(AImageReader, delete);
LOAD(AImageReader, getWindow);
LOAD(AImageReader, acquireNextImageAsync);
LOAD(AImageReader, setImageListener);
LOAD(AImage, deleteAsync);
LOAD(AImage, getHardwareBuffer);
LOAD(AImage, getTimestamp);
LOAD(AImage, getCropRect);
LOAD(AImage, getWidth);
LOAD(AImage, getHeight);
#undef LOAD
/* sync_merge is in libsync.so */
p_awh->ndk_sync_lib = dlopen("libsync.so", RTLD_NOW);
if (p_awh->ndk_sync_lib != NULL)
{
p_awh->ndk_air_api.sync_merge = dlsym(p_awh->ndk_sync_lib, "sync_merge");
if (p_awh->ndk_air_api.sync_merge == NULL)
{
dlclose(p_awh->ndk_sync_lib);
p_awh->ndk_sync_lib = NULL;
goto error;
}
}
p_awh->ndk_air_lib = p_library;
return VLC_SUCCESS;
error:
dlclose(p_library);
return VLC_EGENERIC;
}
static int
LoadASurfaceControlAPI(AWindowHandler *p_awh, void *p_library)
{
#define LOAD(object, name) \
p_awh->ndk_asc_api.object.name = dlsym(p_library, #object "_" #name); \
if (p_awh->ndk_asc_api.object.name == NULL) return VLC_EGENERIC
/* API 31+ functions */
LOAD(ASurfaceTransaction, setCrop);
LOAD(ASurfaceTransaction, setPosition);
LOAD(ASurfaceTransaction, setBufferTransform);
LOAD(ASurfaceTransaction, setScale);
LOAD(ASurfaceTransactionStats, getPreviousReleaseFenceFd);
/* API 29+ functions */
LOAD(ASurfaceControl, createFromWindow);
LOAD(ASurfaceControl, release);
LOAD(ASurfaceTransaction, create);
LOAD(ASurfaceTransaction, delete);
LOAD(ASurfaceTransaction, apply);
LOAD(ASurfaceTransaction, setBuffer);
LOAD(ASurfaceTransaction, setVisibility);
LOAD(ASurfaceTransaction, setBufferTransparency);
LOAD(ASurfaceTransaction, setBufferDataSpace);
LOAD(ASurfaceTransaction, setHdrMetadata_smpte2086);
LOAD(ASurfaceTransaction, setHdrMetadata_cta861_3);
LOAD(ASurfaceTransaction, setOnComplete);
LOAD(ASurfaceTransaction, setDesiredPresentTime);
#undef LOAD
return VLC_SUCCESS;
}
/*
* Android NativeWindow (post android 2.3)
*/
static void
LoadNativeWindowAPI(AWindowHandler *p_awh)
{
void *p_library = dlopen("libandroid.so", RTLD_NOW);
if (!p_library)
return;
p_awh->b_has_ndk_ast_api = LoadNDKSurfaceTextureAPI(p_awh, p_library) == VLC_SUCCESS;
p_awh->b_has_ndk_air_api = LoadAimageReaderAPI(p_awh, p_library) == VLC_SUCCESS;
p_awh->b_has_ndk_asc_api = LoadASurfaceControlAPI(p_awh, p_library) == VLC_SUCCESS;
p_awh->p_anw_dl = p_library;
}
static void
AndroidNativeWindow_onMouseEvent(JNIEnv*, jobject, jlong, jint, jint, jint, jint);
static void
AndroidNativeWindow_onWindowSize(JNIEnv*, jobject, jlong, jint, jint );
const JNINativeMethod jni_callbacks[] = {
{ "nativeOnMouseEvent", "(JIIII)V",
(void *)AndroidNativeWindow_onMouseEvent },
{ "nativeOnWindowSize", "(JII)V",
(void *)AndroidNativeWindow_onWindowSize },
};
static int
InitJNIFields(JNIEnv *env, vlc_object_t *p_obj, jobject *jobj, AWindowHandler *awh)
{
static vlc_mutex_t lock = VLC_STATIC_MUTEX;
int i_init_state = -1;
int ret;
jclass clazz;
vlc_mutex_lock(&lock);
if (i_init_state != -1)
goto end;
#define CHECK_EXCEPTION(what, critical) do { \
if( (*env)->ExceptionCheck(env) ) \
{ \
msg_Err(p_obj, "%s failed", what); \
(*env)->ExceptionClear(env); \
if (critical) { \
i_init_state = 0; \
goto end; \
} \
} \
} while( 0 )
#define GET_METHOD(id_clazz, id, str, args, critical) do { \
awh->jfields.id_clazz.id = (*env)->GetMethodID(\
env, awh->jfields.id_clazz.clazz, (str), (args)); \
CHECK_EXCEPTION("GetMethodID("str")", critical); \
} while( 0 )
if (jobj)
{
clazz = (*env)->GetObjectClass(env, jobj);
CHECK_EXCEPTION("AndroidNativeWindow clazz", true);
awh->jfields.AWindow.clazz = (*env)->NewGlobalRef(env, clazz);
(*env)->DeleteLocalRef(env, clazz);
GET_METHOD(AWindow, getVideoSurface,
"getVideoSurface", "()Landroid/view/Surface;", true);
GET_METHOD(AWindow, getSubtitlesSurface,
"getSubtitlesSurface", "()Landroid/view/Surface;", true);
GET_METHOD(AWindow, registerNative,
"registerNative", "(J)I", true);
GET_METHOD(AWindow, unregisterNative,
"unregisterNative", "()V", true);
GET_METHOD(AWindow, setVideoLayout,
"setVideoLayout", "(IIIIII)V", true);
if ((*env)->RegisterNatives(env, awh->jfields.AWindow.clazz, jni_callbacks, 2) < 0)
{
msg_Err(p_obj, "RegisterNatives failed");
i_init_state = 0;
goto end;
}
}
awh->jfields.SurfaceTexture.clazz = NULL;
awh->jfields.Surface.clazz = NULL;
jclass surfacetexture_class =
(*env)->FindClass(env, "android/graphics/SurfaceTexture");
CHECK_EXCEPTION("SurfaceTexture clazz", true);
awh->jfields.SurfaceTexture.clazz =
(*env)->NewGlobalRef(env, surfacetexture_class);
(*env)->DeleteLocalRef(env, surfacetexture_class);
if (awh->jfields.SurfaceTexture.clazz == NULL)
goto end;
GET_METHOD(SurfaceTexture, init_i, "<init>", "(I)V", false);
GET_METHOD(SurfaceTexture, init_iz, "<init>", "(IZ)V", false);
GET_METHOD(SurfaceTexture, init_z, "<init>", "(Z)V", false);
GET_METHOD(SurfaceTexture, updateTexImage,
"updateTexImage", "()V", true);
GET_METHOD(SurfaceTexture, getTransformMatrix,
"getTransformMatrix", "([F)V", true);
GET_METHOD(SurfaceTexture, release,
"release", "()V", true);
GET_METHOD(SurfaceTexture, releaseTexImage,
"releaseTexImage", "()V", false);
GET_METHOD(SurfaceTexture, attachToGLContext,
"attachToGLContext", "(I)V", true);
GET_METHOD(SurfaceTexture, detachFromGLContext,
"detachFromGLContext", "()V", true);
/* We cannot create any SurfaceTexture if we cannot load the SurfaceTexture
* methods. */
if (!awh->jfields.SurfaceTexture.init_i &&
!awh->jfields.SurfaceTexture.init_iz &&
!awh->jfields.SurfaceTexture.init_z)
goto error;
jclass surface_class = (*env)->FindClass(env, "android/view/Surface");
CHECK_EXCEPTION("android/view/Surface class", true);
awh->jfields.Surface.clazz = (*env)->NewGlobalRef(env, surface_class);
(*env)->DeleteLocalRef(env, surface_class);
if (awh->jfields.Surface.clazz == NULL)
goto error;
GET_METHOD(Surface, init_st, "<init>",
"(Landroid/graphics/SurfaceTexture;)V", true);
#undef GET_METHOD
#undef CHECK_EXCEPTION
i_init_state = 1;
end:
ret = i_init_state == 1 ? VLC_SUCCESS : VLC_EGENERIC;
if (ret)
msg_Err(p_obj, "AndroidNativeWindow jni init failed" );
vlc_mutex_unlock(&lock);
return ret;
error:
i_init_state = 0;
if (awh->jfields.SurfaceTexture.clazz)
(*env)->DeleteGlobalRef(env, awh->jfields.SurfaceTexture.clazz);
awh->jfields.SurfaceTexture.clazz = NULL;
if (awh->jfields.Surface.clazz)
(*env)->DeleteGlobalRef(env, awh->jfields.Surface.clazz);
awh->jfields.Surface.clazz = NULL;
vlc_mutex_unlock(&lock);
msg_Err(p_obj, "Failed to load jfields table");
return VLC_EGENERIC;
}
static JNIEnv*
AWindowHandler_getEnv(AWindowHandler *p_awh)
{
if (p_awh->p_jvm == NULL)
return NULL;
return android_getEnvCommon(NULL, p_awh->p_jvm, "AWindowHandler");
}
AWindowHandler *
AWindowHandler_new(vlc_object_t *obj, vlc_window_t *wnd, awh_events_t *p_events)
{
#define AWINDOW_REGISTER_FLAGS_SUCCESS 0x1
#define AWINDOW_REGISTER_FLAGS_HAS_VIDEO_LAYOUT_LISTENER 0x2
AWindowHandler *p_awh;
JNIEnv *p_env;
JavaVM *p_jvm = var_InheritAddress(obj, "android-jvm");
jobject jobj = var_InheritAddress(obj, "drawable-androidwindow");
if (!p_jvm)
{
msg_Err(obj, "libvlc_media_player options not set");
return NULL;
}
p_env = android_getEnvCommon(NULL, p_jvm, "AWindowHandler");
if (!p_env)
{
msg_Err(obj, "can't get JNIEnv");
return NULL;
}
p_awh = calloc(1, sizeof(AWindowHandler));
if (!p_awh)
return NULL;
p_awh->p_jvm = p_jvm;
p_awh->jobj = jobj ? (*p_env)->NewGlobalRef(p_env, jobj) : NULL;
/* Zero the jfields structure before usage. */
p_awh->jfields = (struct vlc_android_jfields) { .AWindow.clazz = NULL };
p_awh->wnd = wnd;
if (p_events)
p_awh->event.cb = *p_events;
jfloatArray jarray = (*p_env)->NewFloatArray(p_env, 16);
if ((*p_env)->ExceptionCheck(p_env))
{
(*p_env)->ExceptionClear(p_env);
free(p_awh);
return NULL;
}
p_awh->stex.jtransform_mtx_array = (*p_env)->NewGlobalRef(p_env, jarray);
(*p_env)->DeleteLocalRef(p_env, jarray);
p_awh->stex.jtransform_mtx = NULL;
if (InitJNIFields(p_env, obj, jobj, p_awh) != VLC_SUCCESS)
{
msg_Err(obj, "InitJNIFields failed");
return NULL;
}
/* If we don't have window, don't register window handler. */
jint flags = 0;
if (p_events != NULL && wnd != NULL)
{
flags = JNI_ANWCALL(CallIntMethod, registerNative,
(jlong)(intptr_t)p_awh);
if ((flags & AWINDOW_REGISTER_FLAGS_SUCCESS) == 0)
{
msg_Err(obj, "AWindow already registered");
if (p_awh->jobj)
(*p_env)->DeleteGlobalRef(p_env, p_awh->jobj);
(*p_env)->DeleteGlobalRef(p_env, p_awh->stex.jtransform_mtx_array);
free(p_awh);
return NULL;
}
}
LoadNativeWindowAPI(p_awh);
msg_Dbg(obj, "has_anw: %d has_ast: %d has_air: %d has_asc: %d",
p_awh->p_anw_dl != NULL, p_awh->b_has_ndk_ast_api,
p_awh->b_has_ndk_air_api, p_awh->b_has_ndk_asc_api);
p_awh->capabilities = 0;
if (wnd && flags & AWINDOW_REGISTER_FLAGS_HAS_VIDEO_LAYOUT_LISTENER)
p_awh->capabilities |= AWH_CAPS_SET_VIDEO_LAYOUT;
if (p_awh->capabilities & AWH_CAPS_SET_VIDEO_LAYOUT && p_events != NULL)
{
/* XXX: HACK: force mediacodec to setup an OpenGL surface when the vout
* is forced to gles2. Indeed, setting b_has_video_layout_listener to
* false will result in mediacodec using a SurfaceTexture for output.
*/
char *vout_modules = var_InheritString(wnd, "vout");
if (vout_modules
&& (strncmp(vout_modules, "gles2", sizeof("gles2") - 1) == 0
|| strncmp(vout_modules, "opengles2", sizeof("opengles2") - 1) == 0))
{
p_awh->capabilities &= ~AWH_CAPS_SET_VIDEO_LAYOUT;
}
free(vout_modules);
}
p_awh->capabilities |= AWH_CAPS_SURFACE_VIEW;
return p_awh;
}
AWindowHandler *
AWindowHandler_newFromANWs(vlc_object_t *obj, ANativeWindow *video,
ANativeWindow *subtitle)
{
(void) obj;
AWindowHandler *awh = calloc(1, sizeof(AWindowHandler));
if (!awh)
return NULL;
awh->p_jvm = NULL;
awh->jobj = NULL;
awh->jfields = (struct vlc_android_jfields) { .AWindow.clazz = NULL };
awh->wnd = NULL;
LoadNativeWindowAPI(awh);
awh->views[AWindow_Video].p_anw = video;
ANativeWindow_acquire(video);
awh->capabilities = 0;
awh->views[AWindow_Subtitles].p_anw = subtitle;
if (subtitle != NULL)
ANativeWindow_acquire(subtitle);
return awh;
}
static void
AWindowHandler_releaseANativeWindowEnv(AWindowHandler *p_awh, JNIEnv *p_env,
enum AWindow_ID id)
{
assert(id < AWindow_Max);
if (p_awh->views[id].p_anw)
{
ANativeWindow_release(p_awh->views[id].p_anw);
p_awh->views[id].p_anw = NULL;
}
if (p_env != NULL && p_awh->views[id].jsurface)
{
(*p_env)->DeleteGlobalRef(p_env, p_awh->views[id].jsurface);
p_awh->views[id].jsurface = NULL;
}
}
void
AWindowHandler_destroy(AWindowHandler *p_awh)
{
JNIEnv *p_env = AWindowHandler_getEnv(p_awh);
if (p_env)
{
if (p_awh->jfields.SurfaceTexture.clazz)
(*p_env)->DeleteGlobalRef(p_env, p_awh->jfields.SurfaceTexture.clazz);
if (p_awh->jfields.Surface.clazz)
(*p_env)->DeleteGlobalRef(p_env, p_awh->jfields.Surface.clazz);
if (p_awh->jobj)
JNI_ANWCALL(CallVoidMethod, unregisterNative);
}
AWindowHandler_releaseANativeWindowEnv(p_awh, p_env, AWindow_Video);
AWindowHandler_releaseANativeWindowEnv(p_awh, p_env, AWindow_Subtitles);
if (p_env != NULL)
{
if (p_awh->jobj)
(*p_env)->DeleteGlobalRef(p_env, p_awh->jobj);
(*p_env)->DeleteGlobalRef(p_env, p_awh->stex.jtransform_mtx_array);
}
if (p_awh->ndk_sync_lib)
dlclose(p_awh->ndk_sync_lib);
if (p_awh->ndk_air_lib)
dlclose(p_awh->ndk_air_lib);
if (p_awh->p_anw_dl)
dlclose(p_awh->p_anw_dl);
free(p_awh);
}
struct aimage_reader_api *
AWindowHandler_getAImageReaderApi(AWindowHandler *p_awh)
{
return p_awh->b_has_ndk_air_api && p_awh->b_has_ndk_asc_api ?
&p_awh->ndk_air_api : NULL;
}
struct asurface_control_api *
AWindowHandler_getASurfaceControlApi(AWindowHandler *p_awh)
{
return p_awh->b_has_ndk_air_api && p_awh->b_has_ndk_asc_api ?
&p_awh->ndk_asc_api : NULL;
}
static struct vlc_asurfacetexture_priv* CreateSurfaceTexture(
AWindowHandler *p_awh, JNIEnv *p_env, bool single_buffer)
{
/* Needed in case of old API, see comments below. */
EGLDisplay display = EGL_NO_DISPLAY;
EGLSurface surface = EGL_NO_SURFACE;
EGLContext context = EGL_NO_CONTEXT;
EGLContext current_context = EGL_NO_CONTEXT;
EGLContext current_surface = EGL_NO_SURFACE;
jobject surfacetexture;
struct vlc_asurfacetexture_priv *handle = malloc(sizeof *handle);
if (handle == NULL)
return NULL;
handle->awh = p_awh;
handle->texture = NULL;
handle->jtexture = NULL;
handle->surface.jsurface = NULL;
handle->surface.window = NULL;
handle->surface.ops = NULL;
/* API 26 */
if (p_awh->jfields.SurfaceTexture.init_z == NULL)
goto init_iz;
msg_Dbg(p_awh->wnd, "Using SurfaceTexture constructor init_z");
/* We can create a SurfaceTexture in detached mode directly */
surfacetexture = (*p_env)->NewObject(p_env,
p_awh->jfields.SurfaceTexture.clazz, p_awh->jfields.SurfaceTexture.init_z, single_buffer);
if (surfacetexture == NULL)
goto error;
goto success;
init_iz:
msg_Dbg(p_awh->wnd, "Initializing OpenGL context to create SurfaceTexture");
/* Old Android APIs are constructing SurfaceTexture in an attached state
* so we need a dummy context before detaching it, for any other
* constructor than the previous one. That's crap.
* At least, Android EGL display are using reference counting so we don't
* need to care about display lifecycle. */
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
EGLint major, minor;
if (eglInitialize(display, &major, &minor) != EGL_TRUE)
goto error;
current_context = eglGetCurrentContext();
current_surface = eglGetCurrentSurface(EGL_READ);
static const EGLint conf_attr[] = {
EGL_RED_SIZE, 5,
EGL_GREEN_SIZE, 5,
EGL_BLUE_SIZE, 5,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_NONE
};
EGLConfig cfgv[1];
EGLint cfgc;
if (eglChooseConfig(display, conf_attr, cfgv, 1, &cfgc) != EGL_TRUE
|| cfgc == 0)
{
goto error;
}
static const EGLint surface_attr[] =
{
EGL_WIDTH, 1,
EGL_HEIGHT, 1,
EGL_NONE,
};
surface = eglCreatePbufferSurface(display, cfgv[0], surface_attr);
if (surface == EGL_NO_SURFACE)
goto error;
static const EGLint context_attr[] =
{
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE,
};
context = eglCreateContext(display, cfgv[0], EGL_NO_CONTEXT,
context_attr);
eglMakeCurrent(display, surface, surface, context);
/* We'll need to reserve a name in the opengl context */
GLuint texture;
glGenTextures(1, &texture);
/* API 19 */
if (p_awh->jfields.SurfaceTexture.init_iz == NULL)
goto init_i;
msg_Dbg(p_awh->wnd, "Using SurfaceTexture constructor init_iz");
surfacetexture = (*p_env)->NewObject(p_env,
p_awh->jfields.SurfaceTexture.clazz, p_awh->jfields.SurfaceTexture.init_iz, texture, single_buffer);
if (surfacetexture == NULL)
goto error;
goto success;
init_i:
/* We can't get here without this constructor being loaded. */
assert(p_awh->jfields.SurfaceTexture.init_i != NULL);
msg_Dbg(p_awh->wnd, "Using SurfaceTexture constructor init_i");
if (single_buffer)
goto error;
surfacetexture = (*p_env)->NewObject(p_env,
p_awh->jfields.SurfaceTexture.clazz, p_awh->jfields.SurfaceTexture.init_i, texture);
if (surfacetexture == NULL)
goto error;
/* fall-through success */
success:
handle->jtexture = (*p_env)->NewGlobalRef(p_env, surfacetexture);
(*p_env)->DeleteLocalRef(p_env, surfacetexture);
if (handle->jtexture == NULL)
goto error;
if (p_awh->b_has_ndk_ast_api)
{
msg_Dbg(p_awh->wnd, "Using NDK API to init vlc_asurfacetexture");
handle->texture = p_awh->ndk_ast_api.pf_astFromst(p_env, handle->jtexture);
handle->surface.ops = &NDKSurfaceAPI;
handle->surface.window = p_awh->ndk_ast_api.pf_acquireAnw(handle->texture);
jobject jsurface = p_awh->ndk_ast_api.pf_anwToSurface(p_env, handle->surface.window);
if (jsurface == NULL)
goto error;
handle->surface.jsurface = (*p_env)->NewGlobalRef(p_env, jsurface);
(*p_env)->DeleteLocalRef(p_env, jsurface);
if (handle->surface.jsurface == NULL)
goto error;
}
else
{
msg_Dbg(p_awh->wnd, "Using JNI API to init vlc_asurfacetexture");
handle->texture = NULL;
/* Create Surface(SurfaceTexture), ie. producer side of the buffer
* queue in Android. */
jobject jsurface = (*p_env)->NewObject(p_env,
p_awh->jfields.Surface.clazz, p_awh->jfields.Surface.init_st, handle->jtexture);
if (!jsurface)
goto error;
handle->surface.jsurface = (*p_env)->NewGlobalRef(p_env, jsurface);
(*p_env)->DeleteLocalRef(p_env, jsurface);
if (handle->surface.jsurface == NULL)
goto error;
handle->surface.window =
ANativeWindow_fromSurface(p_env, handle->surface.jsurface);
handle->surface.ops = &JNISurfaceAPI;
}
msg_Dbg(p_awh->wnd, "Successfully initialized SurfaceTexture");
if (display != EGL_NO_DISPLAY)
{
handle->surface.ops->detach_from_gl_context(&handle->surface);
glDeleteTextures(1, &texture);
eglMakeCurrent(display, current_surface, current_surface, current_context);
eglDestroyContext(display, context);
eglDestroySurface(display, surface);
eglTerminate(display);
}
return handle;
error:
if (context != EGL_NO_CONTEXT)
{
glDeleteTextures(1, &texture);
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(display, context);
}
if (surface != EGL_NO_SURFACE)
eglDestroySurface(display, surface);
if (display != EGL_NO_DISPLAY)
eglTerminate(display);
if (handle->surface.window != NULL)
ANativeWindow_release(handle->surface.window);
if (handle->surface.jsurface != NULL)
(*p_env)->DeleteGlobalRef(p_env, handle->surface.jsurface);
if (handle->texture != NULL)
p_awh->ndk_ast_api.pf_releaseAst(p_awh->ndk_ast_api.p_ast);
if (handle->jtexture != NULL)
(*p_env)->DeleteGlobalRef(p_env, handle->jtexture);
free(handle);
return NULL;
}
struct vlc_asurfacetexture *
vlc_asurfacetexture_New(AWindowHandler *p_awh, bool single_buffer)
{
if (p_awh->p_jvm == NULL)
return NULL;
JNIEnv *p_env = android_getEnvCommon(NULL, p_awh->p_jvm, "SurfaceTexture");
struct vlc_asurfacetexture_priv *surfacetexture =
CreateSurfaceTexture(p_awh, p_env, single_buffer);
if (surfacetexture == NULL)
return NULL;
return &surfacetexture->surface;
}
static int
WindowHandler_NewSurfaceEnv(AWindowHandler *p_awh, JNIEnv *p_env,
enum AWindow_ID id)
{
jobject jsurface;
switch (id)
{
case AWindow_Video:
if (p_awh->wnd == NULL) return VLC_EGENERIC;
assert(p_awh->jfields.AWindow.getVideoSurface != NULL);
jsurface = JNI_ANWCALL(CallObjectMethod, getVideoSurface);
break;
case AWindow_Subtitles:
if (p_awh->wnd == NULL) return VLC_EGENERIC;
assert(p_awh->jfields.AWindow.getSubtitlesSurface!= NULL);
jsurface = JNI_ANWCALL(CallObjectMethod, getSubtitlesSurface);
break;
default:
vlc_assert_unreachable();
}
if (!jsurface)
return VLC_EGENERIC;
p_awh->views[id].jsurface = (*p_env)->NewGlobalRef(p_env, jsurface);
(*p_env)->DeleteLocalRef(p_env, jsurface);
return VLC_SUCCESS;
}
ANativeWindow *
AWindowHandler_getANativeWindow(AWindowHandler *p_awh, enum AWindow_ID id)
{
assert(id < AWindow_Max);
if (p_awh->views[id].p_anw)
return p_awh->views[id].p_anw;
JNIEnv *p_env = AWindowHandler_getEnv(p_awh);
if (!p_env)
return NULL;
if (WindowHandler_NewSurfaceEnv(p_awh, p_env, id) != VLC_SUCCESS)
return NULL;
assert(p_awh->views[id].jsurface != NULL);
if (!p_awh->views[id].p_anw)
p_awh->views[id].p_anw = ANativeWindow_fromSurface(p_env,
p_awh->views[id].jsurface);
return p_awh->views[id].p_anw;
}
void AWindowHandler_releaseANativeWindow(AWindowHandler *p_awh,
enum AWindow_ID id)
{
JNIEnv *p_env = AWindowHandler_getEnv(p_awh);
AWindowHandler_releaseANativeWindowEnv(p_awh, p_env, id);
}
static inline AWindowHandler *jlong_AWindowHandler(jlong handle)
{
return (AWindowHandler *)(intptr_t) handle;
}
static void
AndroidNativeWindow_onMouseEvent(JNIEnv* env, jobject clazz, jlong handle,
jint action, jint button, jint x, jint y)
{
(void) env; (void) clazz;
AWindowHandler *p_awh = jlong_AWindowHandler(handle);
p_awh->event.cb.on_new_mouse_coords(p_awh->wnd,
& (struct awh_mouse_coords) { action, button, x, y });
}
static void
AndroidNativeWindow_onWindowSize(JNIEnv* env, jobject clazz, jlong handle,
jint width, jint height)
{
(void) env; (void) clazz;
AWindowHandler *p_awh = jlong_AWindowHandler(handle);
if (width >= 0 && height >= 0)
p_awh->event.cb.on_new_window_size(p_awh->wnd, width, height);
}
int
AWindowHandler_getCapabilities(AWindowHandler *p_awh)
{
return p_awh->capabilities;
}
int
AWindowHandler_setVideoLayout(AWindowHandler *p_awh,
int i_display_width, int i_display_height,
const vout_display_place_t *place)
{
assert(p_awh->capabilities & AWH_CAPS_SET_VIDEO_LAYOUT);
JNIEnv *p_env = AWindowHandler_getEnv(p_awh);
if (!p_env)
return VLC_EGENERIC;
vout_display_place_t zero = {0};
if (place == NULL)
place = &zero;
JNI_ANWCALL(CallVoidMethod, setVideoLayout, i_display_width, i_display_height,
(int)place->width, (int)place->height, place->x, place->y);
return VLC_SUCCESS;
}
int
SurfaceTexture_attachToGLContext(struct vlc_asurfacetexture *st, uint32_t tex_name)
{
return st->ops->attach_to_gl_context(st, tex_name);
}
void
SurfaceTexture_detachFromGLContext(struct vlc_asurfacetexture *st)
{
st->ops->detach_from_gl_context(st);
}
int
SurfaceTexture_updateTexImage(struct vlc_asurfacetexture *st, const float **pp_transform_mtx)
{
return st->ops->update_tex_image(st, pp_transform_mtx);
}
void
SurfaceTexture_releaseTexImage(struct vlc_asurfacetexture *st)
{
return st->ops->release_tex_image(st);
}