diff --git a/vulkan/android/.gitignore b/vulkan/android/.gitignore
new file mode 100644
index 0000000000..553fb0d480
--- /dev/null
+++ b/vulkan/android/.gitignore
@@ -0,0 +1,10 @@
+.cxx/
+.externalNativeBuild/
+.gradle/
+.idea/
+assets/
+build/
+gst-build-*/
+local.properties
+src/org/freedesktop/gstreamer/GStreamer.java
+*.iml
diff --git a/vulkan/android/AndroidManifest.xml b/vulkan/android/AndroidManifest.xml
new file mode 100644
index 0000000000..1441174ada
--- /dev/null
+++ b/vulkan/android/AndroidManifest.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vulkan/android/build.gradle b/vulkan/android/build.gradle
new file mode 100644
index 0000000000..6fff9e990b
--- /dev/null
+++ b/vulkan/android/build.gradle
@@ -0,0 +1,89 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 24
+ buildToolsVersion '28.0.3'
+
+ defaultConfig {
+ applicationId "org.freedesktop.gstreamer.vulkan.vulkan_1"
+ minSdkVersion 24
+ targetSdkVersion 24
+ versionCode 1
+ versionName "1.0"
+ archivesBaseName = "$applicationId-v$versionCode"
+
+ externalNativeBuild {
+ ndkBuild {
+ def gstRoot
+
+ if (project.hasProperty('gstAndroidRoot'))
+ gstRoot = project.gstAndroidRoot
+ else
+ gstRoot = System.env.GSTREAMER_ROOT_ANDROID
+
+ if (gstRoot == null)
+ throw new GradleException('GSTREAMER_ROOT_ANDROID must be set, or "gstAndroidRoot" must be defined in your gradle.properties in the top level directory of the unpacked universal GStreamer Android binaries')
+
+ arguments "NDK_APPLICATION_MK=jni/Application.mk", "GSTREAMER_JAVA_SRC_DIR=src", "GSTREAMER_ROOT_ANDROID=$gstRoot", "GSTREAMER_ASSETS_DIR=src/assets"
+
+ targets "vulkan-1"
+
+ // All archs except MIPS and MIPS64 are supported
+ abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
+ }
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ sourceSets {
+ main {
+ manifest.srcFile 'AndroidManifest.xml'
+ java.srcDirs = ['src']
+ resources.srcDirs = ['src']
+ aidl.srcDirs = ['src']
+ renderscript.srcDirs = ['src']
+ res.srcDirs = ['res']
+ assets.srcDirs = ['assets']
+ }
+ }
+ }
+ }
+
+ externalNativeBuild {
+ ndkBuild {
+ path 'jni/Android.mk'
+ }
+ }
+}
+
+afterEvaluate {
+ if (project.hasProperty('compileDebugJavaWithJavac'))
+ project.compileDebugJavaWithJavac.dependsOn 'externalNativeBuildDebug'
+ if (project.hasProperty('compileReleaseJavaWithJavac'))
+ project.compileReleaseJavaWithJavac.dependsOn 'externalNativeBuildRelease'
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ testImplementation 'junit:junit:4.12'
+ implementation 'com.android.support:appcompat-v7:23.1.1'
+}
+
+buildscript {
+ repositories {
+ jcenter()
+ google()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.5.0'
+ }
+}
+
+repositories {
+ jcenter()
+ google()
+}
\ No newline at end of file
diff --git a/vulkan/android/gradle.properties b/vulkan/android/gradle.properties
new file mode 100644
index 0000000000..86aecfc4fb
--- /dev/null
+++ b/vulkan/android/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+# gstAndroidRoot can be set to point to the unpacked GStreamer android top-level directory
+# containing each architecture in subdirectories, or else set the GSTREAMER_ROOT_ANDROID
+# environment variable to that location
+# gstAndroidRoot=/path/to/gstreamer/android
\ No newline at end of file
diff --git a/vulkan/android/gradle/wrapper/gradle-wrapper.jar b/vulkan/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..05ef575b0c
Binary files /dev/null and b/vulkan/android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/vulkan/android/gradle/wrapper/gradle-wrapper.properties b/vulkan/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..c1bad43dd3
--- /dev/null
+++ b/vulkan/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sat Apr 21 19:58:19 WEST 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
diff --git a/vulkan/android/gradlew b/vulkan/android/gradlew
new file mode 100755
index 0000000000..9d82f78915
--- /dev/null
+++ b/vulkan/android/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/vulkan/android/gradlew.bat b/vulkan/android/gradlew.bat
new file mode 100644
index 0000000000..aec99730b4
--- /dev/null
+++ b/vulkan/android/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/vulkan/android/jni/Android.mk b/vulkan/android/jni/Android.mk
new file mode 100644
index 0000000000..c7c932b265
--- /dev/null
+++ b/vulkan/android/jni/Android.mk
@@ -0,0 +1,34 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := vulkan-1
+LOCAL_SRC_FILES := vulkan-1.c dummy.cpp
+LOCAL_SHARED_LIBRARIES := gstreamer_android
+LOCAL_LDLIBS := -llog -landroid
+include $(BUILD_SHARED_LIBRARY)
+
+ifndef GSTREAMER_ROOT_ANDROID
+$(error GSTREAMER_ROOT_ANDROID is not defined!)
+endif
+
+ifeq ($(TARGET_ARCH_ABI),armeabi)
+GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm
+else ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
+GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/armv7
+else ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
+GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm64
+else ifeq ($(TARGET_ARCH_ABI),x86)
+GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86
+else ifeq ($(TARGET_ARCH_ABI),x86_64)
+GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86_64
+else
+$(error Target arch ABI not supported: $(TARGET_ARCH_ABI))
+endif
+
+GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/
+include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk
+GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE) $(GSTREAMER_PLUGINS_SYS) $(GSTREAMER_PLUGINS_EFFECTS) vulkan androidmedia
+GSTREAMER_EXTRA_DEPS := gstreamer-video-1.0 gobject-2.0
+GSTREAMER_EXTRA_LIBS := -liconv
+include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk
diff --git a/vulkan/android/jni/Application.mk b/vulkan/android/jni/Application.mk
new file mode 100644
index 0000000000..1f4ab316f5
--- /dev/null
+++ b/vulkan/android/jni/Application.mk
@@ -0,0 +1,2 @@
+APP_ABI = armeabi armeabi-v7a arm64-v8a x86 x86_64
+APP_STL = c++_shared
\ No newline at end of file
diff --git a/vulkan/android/jni/dummy.cpp b/vulkan/android/jni/dummy.cpp
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/vulkan/android/jni/vulkan-1.c b/vulkan/android/jni/vulkan-1.c
new file mode 100644
index 0000000000..124e8cc4a8
--- /dev/null
+++ b/vulkan/android/jni/vulkan-1.c
@@ -0,0 +1,403 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+GST_DEBUG_CATEGORY_STATIC (debug_category);
+#define GST_CAT_DEFAULT debug_category
+
+/*
+ * These macros provide a way to store the native pointer to CustomData, which might be 32 or 64 bits, into
+ * a jlong, which is always 64 bits, without warnings.
+ */
+#if GLIB_SIZEOF_VOID_P == 8
+# define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(*env)->GetLongField (env, thiz, fieldID)
+# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)data)
+#else
+# define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(jint)(*env)->GetLongField (env, thiz, fieldID)
+# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(jint)data)
+#endif
+
+/* Structure to contain all our information, so we can pass it to callbacks */
+typedef struct _CustomData
+{
+ jobject app; /* Application instance, used to call its methods. A global reference is kept. */
+ GstElement *pipeline; /* The running pipeline */
+ GMainContext *context; /* GLib context used to run the main loop */
+ GMainLoop *main_loop; /* GLib main loop */
+ gboolean initialized; /* To avoid informing the UI multiple times about the initialization */
+ GstElement *video_sink; /* The video sink element which receives XOverlay commands */
+ ANativeWindow *native_window; /* The Android native window where video will be rendered */
+} CustomData;
+
+/* These global variables cache values which are not changing during execution */
+static pthread_t gst_app_thread;
+static pthread_key_t current_jni_env;
+static JavaVM *java_vm;
+static jfieldID custom_data_field_id;
+static jmethodID set_message_method_id;
+static jmethodID on_gstreamer_initialized_method_id;
+
+/*
+ * Private methods
+ */
+
+/* Register this thread with the VM */
+static JNIEnv *
+attach_current_thread (void)
+{
+ JNIEnv *env;
+ JavaVMAttachArgs args;
+
+ GST_DEBUG ("Attaching thread %p", g_thread_self ());
+ args.version = JNI_VERSION_1_4;
+ args.name = NULL;
+ args.group = NULL;
+
+ if ((*java_vm)->AttachCurrentThread (java_vm, &env, &args) < 0) {
+ GST_ERROR ("Failed to attach current thread");
+ return NULL;
+ }
+
+ return env;
+}
+
+/* Unregister this thread from the VM */
+static void
+detach_current_thread (void *env)
+{
+ GST_DEBUG ("Detaching thread %p", g_thread_self ());
+ (*java_vm)->DetachCurrentThread (java_vm);
+}
+
+/* Retrieve the JNI environment for this thread */
+static JNIEnv *
+get_jni_env (void)
+{
+ JNIEnv *env;
+
+ if ((env = pthread_getspecific (current_jni_env)) == NULL) {
+ env = attach_current_thread ();
+ pthread_setspecific (current_jni_env, env);
+ }
+
+ return env;
+}
+
+/* Change the content of the UI's TextView */
+static void
+set_ui_message (const gchar * message, CustomData * data)
+{
+ JNIEnv *env = get_jni_env ();
+ GST_DEBUG ("Setting message to: %s", message);
+ jstring jmessage = (*env)->NewStringUTF (env, message);
+ (*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage);
+ if ((*env)->ExceptionCheck (env)) {
+ GST_ERROR ("Failed to call Java method");
+ (*env)->ExceptionClear (env);
+ }
+ (*env)->DeleteLocalRef (env, jmessage);
+}
+
+/* Retrieve errors from the bus and show them on the UI */
+static void
+error_cb (GstBus * bus, GstMessage * msg, CustomData * data)
+{
+ GError *err;
+ gchar *debug_info;
+ gchar *message_string;
+
+ gst_message_parse_error (msg, &err, &debug_info);
+ message_string =
+ g_strdup_printf ("Error received from element %s: %s",
+ GST_OBJECT_NAME (msg->src), err->message);
+ g_clear_error (&err);
+ g_free (debug_info);
+ set_ui_message (message_string, data);
+ g_free (message_string);
+ gst_element_set_state (data->pipeline, GST_STATE_NULL);
+}
+
+/* Notify UI about pipeline state changes */
+static void
+state_changed_cb (GstBus * bus, GstMessage * msg, CustomData * data)
+{
+ GstState old_state, new_state, pending_state;
+ gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
+ /* Only pay attention to messages coming from the pipeline, not its children */
+ if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) {
+ gchar *message = g_strdup_printf ("State changed to %s",
+ gst_element_state_get_name (new_state));
+ set_ui_message (message, data);
+ g_free (message);
+ }
+}
+
+/* Check if all conditions are met to report GStreamer as initialized.
+ * These conditions will change depending on the application */
+static void
+check_initialization_complete (CustomData * data)
+{
+ JNIEnv *env = get_jni_env ();
+ if (!data->initialized && data->native_window && data->main_loop) {
+ GST_DEBUG
+ ("Initialization complete, notifying application. native_window:%p main_loop:%p",
+ data->native_window, data->main_loop);
+
+ /* The main loop is running and we received a native window, inform the sink about it */
+ gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->video_sink),
+ (guintptr) data->native_window);
+
+ (*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id);
+ if ((*env)->ExceptionCheck (env)) {
+ GST_ERROR ("Failed to call Java method");
+ (*env)->ExceptionClear (env);
+ }
+ data->initialized = TRUE;
+ }
+}
+
+/* Main method for the native code. This is executed on its own thread. */
+static void *
+app_function (void *userdata)
+{
+ JavaVMAttachArgs args;
+ GstBus *bus;
+ CustomData *data = (CustomData *) userdata;
+ GSource *bus_source;
+ GError *error = NULL;
+
+ GST_DEBUG ("Creating pipeline in CustomData at %p", data);
+
+ /* Create our own GLib Main Context and make it the default one */
+ data->context = g_main_context_new ();
+ g_main_context_push_thread_default (data->context);
+
+ /* Build pipeline */
+ data->pipeline =
+ gst_parse_launch
+ ("videotestsrc ! warptv ! videoconvert ! vulkanupload ! vulkancolorconvert ! vulkansink",
+ &error);
+ if (error) {
+ gchar *message =
+ g_strdup_printf ("Unable to build pipeline: %s", error->message);
+ g_clear_error (&error);
+ set_ui_message (message, data);
+ g_free (message);
+ return NULL;
+ }
+
+ /* Set the pipeline to READY, so it can already accept a window handle, if we have one */
+ gst_element_set_state (data->pipeline, GST_STATE_READY);
+
+ data->video_sink =
+ gst_bin_get_by_interface (GST_BIN (data->pipeline),
+ GST_TYPE_VIDEO_OVERLAY);
+ if (!data->video_sink) {
+ GST_ERROR ("Could not retrieve video sink");
+ return NULL;
+ }
+
+ /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
+ bus = gst_element_get_bus (data->pipeline);
+ bus_source = gst_bus_create_watch (bus);
+ g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func,
+ NULL, NULL);
+ g_source_attach (bus_source, data->context);
+ g_source_unref (bus_source);
+ g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb,
+ data);
+ g_signal_connect (G_OBJECT (bus), "message::state-changed",
+ (GCallback) state_changed_cb, data);
+ gst_object_unref (bus);
+
+ /* Create a GLib Main Loop and set it to run */
+ GST_DEBUG ("Entering main loop... (CustomData:%p)", data);
+ data->main_loop = g_main_loop_new (data->context, FALSE);
+ check_initialization_complete (data);
+ g_main_loop_run (data->main_loop);
+ GST_DEBUG ("Exited main loop");
+ g_main_loop_unref (data->main_loop);
+ data->main_loop = NULL;
+
+ /* Free resources */
+ g_main_context_pop_thread_default (data->context);
+ g_main_context_unref (data->context);
+ gst_element_set_state (data->pipeline, GST_STATE_NULL);
+ gst_object_unref (data->video_sink);
+ gst_object_unref (data->pipeline);
+
+ return NULL;
+}
+
+/*
+ * Java Bindings
+ */
+
+/* Instruct the native code to create its internal data structure, pipeline and thread */
+static void
+gst_native_init (JNIEnv * env, jobject thiz)
+{
+ CustomData *data = g_new0 (CustomData, 1);
+ SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data);
+ GST_DEBUG_CATEGORY_INIT (debug_category, "vulkan-1", 0, "Android vulkan 1");
+ gst_debug_set_threshold_for_name ("vulkan-1", GST_LEVEL_DEBUG);
+ gst_debug_set_threshold_from_string ("3,*vulkan*:7", FALSE);
+ GST_DEBUG ("Created CustomData at %p", data);
+ data->app = (*env)->NewGlobalRef (env, thiz);
+ GST_DEBUG ("Created GlobalRef for app object at %p", data->app);
+ pthread_create (&gst_app_thread, NULL, &app_function, data);
+}
+
+/* Quit the main loop, remove the native thread and free resources */
+static void
+gst_native_finalize (JNIEnv * env, jobject thiz)
+{
+ CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
+ if (!data)
+ return;
+ GST_DEBUG ("Quitting main loop...");
+ g_main_loop_quit (data->main_loop);
+ GST_DEBUG ("Waiting for thread to finish...");
+ pthread_join (gst_app_thread, NULL);
+ GST_DEBUG ("Deleting GlobalRef for app object at %p", data->app);
+ (*env)->DeleteGlobalRef (env, data->app);
+ GST_DEBUG ("Freeing CustomData at %p", data);
+ g_free (data);
+ SET_CUSTOM_DATA (env, thiz, custom_data_field_id, NULL);
+ GST_DEBUG ("Done finalizing");
+}
+
+/* Set pipeline to PLAYING state */
+static void
+gst_native_play (JNIEnv * env, jobject thiz)
+{
+ CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
+ if (!data)
+ return;
+ GST_DEBUG ("Setting state to PLAYING");
+ gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
+}
+
+/* Set pipeline to PAUSED state */
+static void
+gst_native_pause (JNIEnv * env, jobject thiz)
+{
+ CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
+ if (!data)
+ return;
+ GST_DEBUG ("Setting state to PAUSED");
+ gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
+}
+
+/* Static class initializer: retrieve method and field IDs */
+static jboolean
+gst_native_class_init (JNIEnv * env, jclass klass)
+{
+ custom_data_field_id =
+ (*env)->GetFieldID (env, klass, "native_custom_data", "J");
+ set_message_method_id =
+ (*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V");
+ on_gstreamer_initialized_method_id =
+ (*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V");
+
+ if (!custom_data_field_id || !set_message_method_id
+ || !on_gstreamer_initialized_method_id) {
+ /* We emit this message through the Android log instead of the GStreamer log because the later
+ * has not been initialized yet.
+ */
+ __android_log_print (ANDROID_LOG_ERROR, "vulkan-1",
+ "The calling class does not implement all necessary interface methods");
+ return JNI_FALSE;
+ }
+ return JNI_TRUE;
+}
+
+static void
+gst_native_surface_init (JNIEnv * env, jobject thiz, jobject surface)
+{
+ CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
+ if (!data)
+ return;
+ ANativeWindow *new_native_window = ANativeWindow_fromSurface (env, surface);
+ GST_DEBUG ("Received surface %p (native window %p)", surface,
+ new_native_window);
+
+ if (data->native_window) {
+ ANativeWindow_release (data->native_window);
+ if (data->native_window == new_native_window) {
+ GST_DEBUG ("New native window is the same as the previous one %p",
+ data->native_window);
+ if (data->video_sink) {
+ gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->video_sink));
+ gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->video_sink));
+ }
+ return;
+ } else {
+ GST_DEBUG ("Released previous native window %p", data->native_window);
+ data->initialized = FALSE;
+ }
+ }
+ data->native_window = new_native_window;
+
+ check_initialization_complete (data);
+}
+
+static void
+gst_native_surface_finalize (JNIEnv * env, jobject thiz)
+{
+ CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
+ if (!data)
+ return;
+ GST_DEBUG ("Releasing Native Window %p", data->native_window);
+
+ if (data->video_sink) {
+ gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->video_sink),
+ (guintptr) NULL);
+ gst_element_set_state (data->pipeline, GST_STATE_READY);
+ }
+
+ ANativeWindow_release (data->native_window);
+ data->native_window = NULL;
+ data->initialized = FALSE;
+}
+
+/* List of implemented native methods */
+static JNINativeMethod native_methods[] = {
+ {"nativeInit", "()V", (void *) gst_native_init},
+ {"nativeFinalize", "()V", (void *) gst_native_finalize},
+ {"nativePlay", "()V", (void *) gst_native_play},
+ {"nativePause", "()V", (void *) gst_native_pause},
+ {"nativeSurfaceInit", "(Ljava/lang/Object;)V",
+ (void *) gst_native_surface_init},
+ {"nativeSurfaceFinalize", "()V", (void *) gst_native_surface_finalize},
+ {"nativeClassInit", "()Z", (void *) gst_native_class_init}
+};
+
+/* Library initializer */
+jint
+JNI_OnLoad (JavaVM * vm, void *reserved)
+{
+ JNIEnv *env = NULL;
+
+ java_vm = vm;
+
+ if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
+ __android_log_print (ANDROID_LOG_ERROR, "vulkan-1",
+ "Could not retrieve JNIEnv");
+ return 0;
+ }
+ jclass klass = (*env)->FindClass (env,
+ "org/freedesktop/gstreamer/vulkan/Vulkan1");
+ (*env)->RegisterNatives (env, klass, native_methods,
+ G_N_ELEMENTS (native_methods));
+
+ pthread_key_create (¤t_jni_env, detach_current_thread);
+
+ return JNI_VERSION_1_4;
+}
diff --git a/vulkan/android/res/layout/main.xml b/vulkan/android/res/layout/main.xml
new file mode 100644
index 0000000000..da2a993996
--- /dev/null
+++ b/vulkan/android/res/layout/main.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vulkan/android/res/values/strings.xml b/vulkan/android/res/values/strings.xml
new file mode 100644
index 0000000000..9c1e03d484
--- /dev/null
+++ b/vulkan/android/res/values/strings.xml
@@ -0,0 +1,6 @@
+
+
+ GStreamer Vulkan 1
+ Play
+ Stop
+
diff --git a/vulkan/android/src/org/freedesktop/gstreamer/vulkan/GStreamerSurfaceView.java b/vulkan/android/src/org/freedesktop/gstreamer/vulkan/GStreamerSurfaceView.java
new file mode 100644
index 0000000000..f040c675f6
--- /dev/null
+++ b/vulkan/android/src/org/freedesktop/gstreamer/vulkan/GStreamerSurfaceView.java
@@ -0,0 +1,85 @@
+package org.freedesktop.gstreamer.vulkan;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.SurfaceView;
+import android.view.View;
+
+// A simple SurfaceView whose width and height can be set from the outside
+public class GStreamerSurfaceView extends SurfaceView {
+ public int media_width = 320;
+ public int media_height = 240;
+
+ // Mandatory constructors, they do not do much
+ public GStreamerSurfaceView(Context context, AttributeSet attrs,
+ int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public GStreamerSurfaceView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public GStreamerSurfaceView (Context context) {
+ super(context);
+ }
+
+ // Called by the layout manager to find out our size and give us some rules.
+ // We will try to maximize our size, and preserve the media's aspect ratio if
+ // we are given the freedom to do so.
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = 0, height = 0;
+ int wmode = View.MeasureSpec.getMode(widthMeasureSpec);
+ int hmode = View.MeasureSpec.getMode(heightMeasureSpec);
+ int wsize = View.MeasureSpec.getSize(widthMeasureSpec);
+ int hsize = View.MeasureSpec.getSize(heightMeasureSpec);
+
+ Log.i ("GStreamer", "onMeasure called with " + media_width + "x" + media_height);
+ // Obey width rules
+ switch (wmode) {
+ case View.MeasureSpec.AT_MOST:
+ if (hmode == View.MeasureSpec.EXACTLY) {
+ width = Math.min(hsize * media_width / media_height, wsize);
+ break;
+ }
+ case View.MeasureSpec.EXACTLY:
+ width = wsize;
+ break;
+ case View.MeasureSpec.UNSPECIFIED:
+ width = media_width;
+ }
+
+ // Obey height rules
+ switch (hmode) {
+ case View.MeasureSpec.AT_MOST:
+ if (wmode == View.MeasureSpec.EXACTLY) {
+ height = Math.min(wsize * media_height / media_width, hsize);
+ break;
+ }
+ case View.MeasureSpec.EXACTLY:
+ height = hsize;
+ break;
+ case View.MeasureSpec.UNSPECIFIED:
+ height = media_height;
+ }
+
+ // Finally, calculate best size when both axis are free
+ if (hmode == View.MeasureSpec.AT_MOST && wmode == View.MeasureSpec.AT_MOST) {
+ int correct_height = width * media_height / media_width;
+ int correct_width = height * media_width / media_height;
+
+ if (correct_height < height)
+ height = correct_height;
+ else
+ width = correct_width;
+ }
+
+ // Obey minimum size
+ width = Math.max (getSuggestedMinimumWidth(), width);
+ height = Math.max (getSuggestedMinimumHeight(), height);
+ setMeasuredDimension(width, height);
+ }
+
+}
diff --git a/vulkan/android/src/org/freedesktop/gstreamer/vulkan/Vulkan1.java b/vulkan/android/src/org/freedesktop/gstreamer/vulkan/Vulkan1.java
new file mode 100644
index 0000000000..3d5ca153ef
--- /dev/null
+++ b/vulkan/android/src/org/freedesktop/gstreamer/vulkan/Vulkan1.java
@@ -0,0 +1,144 @@
+package org.freedesktop.gstreamer.vulkan;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ImageButton;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.freedesktop.gstreamer.GStreamer;
+import org.freedesktop.gstreamer.vulkan.vulkan_1.R;
+
+public class Vulkan1 extends Activity implements SurfaceHolder.Callback {
+ private native void nativeInit(); // Initialize native code, build pipeline, etc
+ private native void nativeFinalize(); // Destroy pipeline and shutdown native code
+ private native void nativePlay(); // Set pipeline to PLAYING
+ private native void nativePause(); // Set pipeline to PAUSED
+ private static native boolean nativeClassInit(); // Initialize native class: cache Method IDs for callbacks
+ private native void nativeSurfaceInit(Object surface);
+ private native void nativeSurfaceFinalize();
+ private long native_custom_data; // Native code will use this to keep private data
+
+ private boolean is_playing_desired; // Whether the user asked to go to PLAYING
+
+ // Called when the activity is first created.
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ // Initialize GStreamer and warn if it fails
+ try {
+ GStreamer.init(this);
+ } catch (Exception e) {
+ Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
+ finish();
+ return;
+ }
+
+ setContentView(R.layout.main);
+
+ ImageButton play = (ImageButton) this.findViewById(R.id.button_play);
+ play.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ is_playing_desired = true;
+ nativePlay();
+ }
+ });
+
+ ImageButton pause = (ImageButton) this.findViewById(R.id.button_stop);
+ pause.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ is_playing_desired = false;
+ nativePause();
+ }
+ });
+
+ SurfaceView sv = (SurfaceView) this.findViewById(R.id.surface_video);
+ SurfaceHolder sh = sv.getHolder();
+ sh.addCallback(this);
+
+ if (savedInstanceState != null) {
+ is_playing_desired = savedInstanceState.getBoolean("playing");
+ Log.i ("GStreamer", "Activity created. Saved state is playing:" + is_playing_desired);
+ } else {
+ is_playing_desired = false;
+ Log.i ("GStreamer", "Activity created. There is no saved state, playing: false");
+ }
+
+ // Start with disabled buttons, until native code is initialized
+ this.findViewById(R.id.button_play).setEnabled(false);
+ this.findViewById(R.id.button_stop).setEnabled(false);
+
+ nativeInit();
+ }
+
+ protected void onSaveInstanceState (Bundle outState) {
+ Log.d ("GStreamer", "Saving state, playing:" + is_playing_desired);
+ outState.putBoolean("playing", is_playing_desired);
+ }
+
+ protected void onDestroy() {
+ nativeFinalize();
+ super.onDestroy();
+ }
+
+ // Called from native code. This sets the content of the TextView from the UI thread.
+ private void setMessage(final String message) {
+ final TextView tv = (TextView) this.findViewById(R.id.textview_message);
+ runOnUiThread (new Runnable() {
+ public void run() {
+ tv.setText(message);
+ }
+ });
+ }
+
+ // Called from native code. Native code calls this once it has created its pipeline and
+ // the main loop is running, so it is ready to accept commands.
+ private void onGStreamerInitialized () {
+ Log.i ("GStreamer", "Gst initialized. Restoring state, playing:" + is_playing_desired);
+ // Restore previous playing state
+ if (is_playing_desired) {
+ nativePlay();
+ } else {
+ nativePause();
+ }
+
+ // Re-enable buttons, now that GStreamer is initialized
+ final Activity activity = this;
+ runOnUiThread(new Runnable() {
+ public void run() {
+ activity.findViewById(R.id.button_play).setEnabled(true);
+ activity.findViewById(R.id.button_stop).setEnabled(true);
+ }
+ });
+ }
+
+ static {
+ System.loadLibrary("gstreamer_android");
+ System.loadLibrary("vulkan-1");
+ nativeClassInit();
+ }
+
+ public void surfaceChanged(SurfaceHolder holder, int format, int width,
+ int height) {
+ Log.d("GStreamer", "Surface changed to format " + format + " width "
+ + width + " height " + height);
+ nativeSurfaceInit (holder.getSurface());
+ }
+
+ public void surfaceCreated(SurfaceHolder holder) {
+ Log.d("GStreamer", "Surface created: " + holder.getSurface());
+ }
+
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ Log.d("GStreamer", "Surface destroyed");
+ nativeSurfaceFinalize ();
+ }
+
+}