[Android/Api] api base
authorJaeyun <jy1210.jung@samsung.com>
Wed, 10 Jul 2019 11:31:14 +0000 (20:31 +0900)
committerMyungJoo Ham <myungjoo.ham@samsung.com>
Thu, 25 Jul 2019 06:31:24 +0000 (15:31 +0900)
Update files for android-java api.

- initial commit to add android java api set.
- single-shot / pipeline functions.
- data callback from sink node in pipeline api.

Signed-off-by: Jaeyun Jung <jy1210.jung@samsung.com>
17 files changed:
api/android/api/AndroidManifest.xml [new file with mode: 0644]
api/android/api/build.gradle [new file with mode: 0644]
api/android/api/jni/Android-nnstreamer.mk [new file with mode: 0644]
api/android/api/jni/Android-tensorflow-lite-prebuilt.mk [new file with mode: 0644]
api/android/api/jni/Android-tensorflow-lite.mk [new file with mode: 0644]
api/android/api/jni/Android.mk [new file with mode: 0644]
api/android/api/jni/Application.mk [new file with mode: 0644]
api/android/api/jni/nnstreamer-native-api.c [new file with mode: 0644]
api/android/api/proguard-rules.pro [new file with mode: 0644]
api/android/api/res/values/strings.xml [new file with mode: 0644]
api/android/api/src/com/samsung/android/nnstreamer/NNStreamer.java [new file with mode: 0644]
api/android/api/src/com/samsung/android/nnstreamer/Pipeline.java [new file with mode: 0644]
api/android/api/src/com/samsung/android/nnstreamer/SingleShot.java [new file with mode: 0644]
api/android/api/src/com/samsung/android/nnstreamer/TensorsData.java [new file with mode: 0644]
api/android/api/src/com/samsung/android/nnstreamer/TensorsInfo.java [new file with mode: 0644]
api/android/build.gradle [new file with mode: 0644]
api/android/settings.gradle [new file with mode: 0644]

diff --git a/api/android/api/AndroidManifest.xml b/api/android/api/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..2ad5e7e
--- /dev/null
@@ -0,0 +1,10 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.samsung.android.nnstreamer" >
+
+    <uses-feature android:glEsVersion="0x00020000"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+    <application
+        android:largeHeap="true" >
+    </application>
+</manifest>
diff --git a/api/android/api/build.gradle b/api/android/api/build.gradle
new file mode 100644 (file)
index 0000000..5606b71
--- /dev/null
@@ -0,0 +1,60 @@
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion 28
+    buildToolsVersion '28.0.3'
+    defaultConfig {
+        minSdkVersion 24
+        targetSdkVersion 28
+        versionCode 1
+        versionName "1.0"
+        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 "nnstreamer-native"
+
+                abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
+            }
+        }
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.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'
+        }
+    }
+    productFlavors {
+    }
+}
+
+dependencies {
+    implementation fileTree(include: ['*.jar'], dir: 'libs')
+    implementation 'com.android.support:appcompat-v7:28.0.0'
+}
diff --git a/api/android/api/jni/Android-nnstreamer.mk b/api/android/api/jni/Android-nnstreamer.mk
new file mode 100644 (file)
index 0000000..785d2b3
--- /dev/null
@@ -0,0 +1,44 @@
+#------------------------------------------------------
+# nnstreamer
+#------------------------------------------------------
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := nnstreamer
+
+ifndef NNSTREAMER_ROOT
+$(error NNSTREAMER_ROOT is not defined!)
+endif
+
+include $(NNSTREAMER_ROOT)/jni/nnstreamer.mk
+
+LOCAL_SRC_FILES := \
+    $(NNSTREAMER_COMMON_SRCS) \
+    $(NNSTREAMER_PLUGINS_SRCS) \
+    $(NNSTREAMER_CAPI_SRCS) \
+    $(NNSTREAMER_FILTER_TFLITE_SRCS) \
+    $(NNSTREAMER_DECODER_BB_SRCS) \
+    $(NNSTREAMER_DECODER_DV_SRCS) \
+    $(NNSTREAMER_DECODER_IL_SRCS) \
+    $(NNSTREAMER_DECODER_PE_SRCS)
+
+LOCAL_C_INCLUDES := \
+    $(NNSTREAMER_INCLUDES) \
+    $(NNSTREAMER_CAPI_INCLUDES)
+
+# common headers (gstreamer, glib)
+LOCAL_C_INCLUDES += \
+    $(GSTREAMER_ROOT)/include/gstreamer-1.0 \
+    $(GSTREAMER_ROOT)/include/glib-2.0 \
+    $(GSTREAMER_ROOT)/lib/glib-2.0/include \
+    $(GSTREAMER_ROOT)/include
+
+# common headers (tensorflow-lite)
+LOCAL_C_INCLUDES += \
+    $(TF_LITE_INCLUDES)
+
+LOCAL_CFLAGS += -O2 -DVERSION=\"$(NNSTREAMER_VERSION)\"
+LOCAL_CXXFLAGS += -std=c++11 -O2 -DVERSION=\"$(NNSTREAMER_VERSION)\"
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/api/android/api/jni/Android-tensorflow-lite-prebuilt.mk b/api/android/api/jni/Android-tensorflow-lite-prebuilt.mk
new file mode 100644 (file)
index 0000000..ffc493c
--- /dev/null
@@ -0,0 +1,35 @@
+#------------------------------------------------------
+# tensorflow-lite
+#
+# This mk file defines tensorflow-lite module with prebuilt static library.
+# To build and run the example with gstreamer binaries, we built a static library (e.g., libtensorflow-lite.a)
+# for Android/Tensorflow-lite from the Tensorflow repository of the Tizen software platform with version 1.9 using the Android-tensorflow-lite.mk.
+# - [Tizen] Tensorflow git repository:
+#    * Repository: https://review.tizen.org/gerrit/p/platform/upstream/tensorflow
+#    * Branch name: sandbox/sangjung/launchpad_bugfix
+#    * Commit: 5c3399d [Debian] add dependency on python 2.7, provides of tensorflow
+#------------------------------------------------------
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := tensorflow-lite
+
+TF_LITE_DIR := $(LOCAL_PATH)/tensorflow-lite
+TF_LITE_INCLUDES := $(TF_LITE_DIR)/include
+
+ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
+TF_LITE_LIB_PATH := $(TF_LITE_DIR)/lib/armv7
+else ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
+TF_LITE_LIB_PATH := $(TF_LITE_DIR)/lib/arm64
+else ifeq ($(TARGET_ARCH_ABI),x86)
+TF_LITE_LIB_PATH := $(TF_LITE_DIR)/lib/x86
+else ifeq ($(TARGET_ARCH_ABI),x86_64)
+TF_LITE_LIB_PATH := $(TF_LITE_DIR)/lib/x86_64
+else
+$(error Target arch ABI not supported: $(TARGET_ARCH_ABI))
+endif
+
+LOCAL_SRC_FILES := $(TF_LITE_LIB_PATH)/libtensorflow-lite.a
+
+include $(PREBUILT_STATIC_LIBRARY)
diff --git a/api/android/api/jni/Android-tensorflow-lite.mk b/api/android/api/jni/Android-tensorflow-lite.mk
new file mode 100644 (file)
index 0000000..4611253
--- /dev/null
@@ -0,0 +1,68 @@
+#------------------------------------------------------
+# tensorflow-lite
+#
+# This mk file is to build a static library from cloned tensorflow repository.
+# To utilize and build a new version, you have to define the root directory and check Makefile to build tensorflow-lite.
+# Now this file is to build tensorflow-lite from tizen tensorflow repository with version 1.9.
+#------------------------------------------------------
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := tensorflow-lite
+
+# Need to set tensorflow root dir
+ifndef TENSORFLOW_ROOT
+$(error TENSORFLOW_ROOT is not defined!)
+endif
+
+# Set tensorflow-lite dir (TODO change this with tensorflow-lite version)
+TF_LITE_DIR := $(TENSORFLOW_ROOT)/tensorflow/contrib/lite
+
+# Set files to compile (TODO check Makefile to build tensorflow-lite)
+CORE_CC_ALL_SRCS := \
+    $(wildcard $(TF_LITE_DIR)/*.cc) \
+    $(wildcard $(TF_LITE_DIR)/kernels/*.cc) \
+    $(wildcard $(TF_LITE_DIR)/kernels/internal/*.cc) \
+    $(wildcard $(TF_LITE_DIR)/kernels/internal/optimized/*.cc) \
+    $(wildcard $(TF_LITE_DIR)/kernels/internal/reference/*.cc) \
+    $(wildcard $(TF_LITE_DIR)/*.c) \
+    $(wildcard $(TF_LITE_DIR)/kernels/*.c) \
+    $(wildcard $(TF_LITE_DIR)/kernels/internal/*.c) \
+    $(wildcard $(TF_LITE_DIR)/kernels/internal/optimized/*.c) \
+    $(wildcard $(TF_LITE_DIR)/kernels/internal/reference/*.c) \
+    $(wildcard $(TF_LITE_DIR)/downloads/farmhash/src/farmhash.cc) \
+    $(wildcard $(TF_LITE_DIR)/downloads/fft2d/fftsg.c)
+
+# Remove any duplicates.
+CORE_CC_ALL_SRCS := $(sort $(CORE_CC_ALL_SRCS))
+CORE_CC_EXCLUDE_SRCS := \
+    $(wildcard $(TF_LITE_DIR)/*test.cc) \
+    $(wildcard $(TF_LITE_DIR)/*/*test.cc) \
+    $(wildcard $(TF_LITE_DIR)/*/*/*test.cc) \
+    $(wildcard $(TF_LITE_DIR)/*/*/*/*test.cc) \
+    $(wildcard $(TF_LITE_DIR)/kernels/test_util.cc) \
+    $(wildcard $(TF_LITE_DIR)/examples/minimal/minimal.cc)
+
+# Filter out all the excluded files.
+TF_LITE_CC_SRCS := $(filter-out $(CORE_CC_EXCLUDE_SRCS), $(CORE_CC_ALL_SRCS))
+TF_LITE_INCLUDES := \
+    $(ANDROID_NDK)/../ \
+    $(TENSORFLOW_ROOT) \
+    $(TF_LITE_DIR)/downloads \
+    $(TF_LITE_DIR)/downloads/eigen \
+    $(TF_LITE_DIR)/downloads/gemmlowp \
+    $(TF_LITE_DIR)/downloads/neon_2_sse \
+    $(TF_LITE_DIR)/downloads/farmhash/src \
+    $(TF_LITE_DIR)/downloads/flatbuffers/include
+
+LOCAL_SRC_FILES := $(TF_LITE_CC_SRCS)
+LOCAL_C_INCLUDES := $(TF_LITE_INCLUDES)
+
+LOCAL_CFLAGS += -O2 -DNDEBUG
+# std for toolchain in NDK
+# rtti for typecast in tensorflow-lite
+# exceptions to enable exception handling in tensorflow-lite
+LOCAL_CXXFLAGS += -std=c++11 -frtti -fexceptions -O2 -DNDEBUG
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/api/android/api/jni/Android.mk b/api/android/api/jni/Android.mk
new file mode 100644 (file)
index 0000000..45c00e2
--- /dev/null
@@ -0,0 +1,64 @@
+LOCAL_PATH := $(call my-dir)
+
+ifndef GSTREAMER_ROOT_ANDROID
+$(error GSTREAMER_ROOT_ANDROID is not defined!)
+endif
+
+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
+
+#------------------------------------------------------
+# external libs
+#------------------------------------------------------
+include $(LOCAL_PATH)/Android-tensorflow-lite.mk
+#include $(LOCAL_PATH)/Android-tensorflow-lite-prebuilt.mk
+
+#------------------------------------------------------
+# nnstreamer
+#------------------------------------------------------
+include $(LOCAL_PATH)/Android-nnstreamer.mk
+
+#------------------------------------------------------
+# native code for api
+#------------------------------------------------------
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := nnstreamer-native
+LOCAL_SRC_FILES := nnstreamer-native-api.c
+LOCAL_CFLAGS += -O2 -DVERSION=\"$(NNSTREAMER_VERSION)\"
+LOCAL_C_INCLUDES := $(NNSTREAMER_CAPI_INCLUDES)
+LOCAL_STATIC_LIBRARIES := nnstreamer tensorflow-lite cpufeatures
+LOCAL_SHARED_LIBRARIES := gstreamer_android
+LOCAL_LDLIBS := -llog -landroid
+
+include $(BUILD_SHARED_LIBRARY)
+
+GSTREAMER_NDK_BUILD_PATH  := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/
+include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk
+# add necessary gstreamer plugins
+GSTREAMER_PLUGINS         := $(GSTREAMER_PLUGINS_CORE) \
+    $(GSTREAMER_PLUGINS_CODECS) \
+    $(GSTREAMER_PLUGINS_ENCODING) \
+    $(GSTREAMER_PLUGINS_PLAYBACK) \
+    $(GSTREAMER_PLUGINS_VIS) \
+    $(GSTREAMER_PLUGINS_SYS) \
+    $(GSTREAMER_PLUGINS_EFFECTS) \
+    $(GSTREAMER_PLUGINS_CAPTURE) \
+    $(GSTREAMER_PLUGINS_CODECS_GPL) \
+    $(GSTREAMER_PLUGINS_CODECS_RESTRICTED) \
+    $(GSTREAMER_PLUGINS_NET_RESTRICTED) \
+    $(GSTREAMER_PLUGINS_GES)
+GSTREAMER_EXTRA_DEPS      := gstreamer-video-1.0 gstreamer-audio-1.0 gstreamer-app-1.0 gobject-2.0
+GSTREAMER_EXTRA_LIBS      := -liconv -lcairo
+include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk
+
+$(call import-module, android/cpufeatures)
diff --git a/api/android/api/jni/Application.mk b/api/android/api/jni/Application.mk
new file mode 100644 (file)
index 0000000..3808c1a
--- /dev/null
@@ -0,0 +1,2 @@
+APP_ABI = armeabi-v7a arm64-v8a x86 x86_64
+APP_STL = c++_shared
diff --git a/api/android/api/jni/nnstreamer-native-api.c b/api/android/api/jni/nnstreamer-native-api.c
new file mode 100644 (file)
index 0000000..e3f46da
--- /dev/null
@@ -0,0 +1,1303 @@
+/**
+ * @file       nnstreamer-native-api.c
+ * @date       10 July 2019
+ * @brief      Native code for NNStreamer API
+ * @author     Jaeyun Jung <jy1210.jung@samsung.com>
+ * @bug                No known bugs except for NYI items
+ */
+
+#include <jni.h>
+#include <android/log.h>
+
+#include <gst/gst.h>
+
+#include "nnstreamer.h"
+#include "nnstreamer-single.h"
+#include "nnstreamer-capi-private.h"
+
+#ifndef DBG
+#define DBG FALSE
+#endif
+
+#define TAG "NNStreamer-native"
+
+#define nns_logi(...) \
+    __android_log_print (ANDROID_LOG_INFO, TAG, __VA_ARGS__)
+
+#define nns_logw(...) \
+    __android_log_print (ANDROID_LOG_WARN, TAG, __VA_ARGS__)
+
+#define nns_loge(...) \
+    __android_log_print (ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
+
+#define nns_logd(...) \
+    __android_log_print (ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
+
+#if (DBG)
+#define print_log nns_logd
+#else
+#define print_log(...)
+#endif
+
+#if GLIB_SIZEOF_VOID_P == 8
+#define CAST_TO_LONG(p) (jlong)(p)
+#define CAST_TO_TYPE(l,type) (type)(l)
+#else
+#define CAST_TO_LONG(p) (jlong)(jint)(p)
+#define CAST_TO_TYPE(l,type) (type)(jint)(l)
+#endif
+
+#define NNS_PIPE_TYPE_PIPELINE "pipeline"
+#define NNS_PIPE_TYPE_SINGLE "single"
+
+#define NNS_ELEMENT_TYPE_SRC "src"
+#define NNS_ELEMENT_TYPE_SINK "sink"
+#define NNS_ELEMENT_TYPE_VALVE "valve"
+#define NNS_ELEMENT_TYPE_SWITCH_IN "switch_in"
+#define NNS_ELEMENT_TYPE_SWITCH_OUT "switch_out"
+
+/**
+ * @brief Struct for constructed pipeline.
+ */
+typedef struct
+{
+  gchar *pipeline_type;
+  gpointer pipeline_handle;
+  GHashTable *element_handles;
+  GMutex lock;
+
+  JavaVM *jvm;
+  jint version;
+  pthread_key_t jni_env;
+
+  jobject instance;
+  jclass cls_tensors_data;
+  jclass cls_tensors_info;
+} pipeline_info_s;
+
+/**
+ * @brief Struct for element data in pipeline.
+ */
+typedef struct
+{
+  gchar *name;
+  gchar *type;
+  gpointer handle;
+  pipeline_info_s *pipe_info;
+} element_data_s;
+
+/**
+ * @brief Attach thread with Java VM.
+ */
+static JNIEnv *
+nns_attach_current_thread (pipeline_info_s * pipe_info)
+{
+  JNIEnv *env;
+  JavaVM *jvm;
+  JavaVMAttachArgs args;
+
+  g_assert (pipe_info);
+  jvm = pipe_info->jvm;
+
+  args.version = pipe_info->version;
+  args.name = NULL;
+  args.group = NULL;
+
+  if ((*jvm)->AttachCurrentThread (jvm, &env, &args) < 0) {
+    nns_loge ("Failed to attach current thread.");
+    return NULL;
+  }
+
+  return env;
+}
+
+/**
+ * @brief Get JNI environment.
+ */
+static JNIEnv *
+nns_get_jni_env (pipeline_info_s * pipe_info)
+{
+  JNIEnv *env;
+
+  g_assert (pipe_info);
+
+  if ((env = pthread_getspecific (pipe_info->jni_env)) == NULL) {
+    env = nns_attach_current_thread (pipe_info);
+    pthread_setspecific (pipe_info->jni_env, env);
+  }
+
+  return env;
+}
+
+/**
+ * @brief Free element handle pointer.
+ */
+static void
+nns_free_element_data (gpointer data)
+{
+  element_data_s *item = (element_data_s *) data;
+
+  if (item) {
+    if (g_str_equal (item->type, NNS_ELEMENT_TYPE_SRC)) {
+      ml_pipeline_src_release_handle ((ml_pipeline_src_h) item->handle);
+    } else if (g_str_equal (item->type, NNS_ELEMENT_TYPE_SINK)) {
+      ml_pipeline_sink_unregister ((ml_pipeline_sink_h) item->handle);
+    } else if (g_str_equal (item->type, NNS_ELEMENT_TYPE_SWITCH_IN) ||
+        g_str_equal (item->type, NNS_ELEMENT_TYPE_SWITCH_OUT)) {
+      ml_pipeline_switch_release_handle ((ml_pipeline_switch_h) item->handle);
+    } else if (g_str_equal (item->type, NNS_ELEMENT_TYPE_VALVE)) {
+      ml_pipeline_valve_release_handle ((ml_pipeline_valve_h) item->handle);
+    } else {
+      nns_logw ("Given element type %s is unknown.", item->type);
+      g_free (item->handle);
+    }
+
+    g_free (item->name);
+    g_free (item->type);
+    g_free (item);
+  }
+}
+
+/**
+ * @brief Construct pipeline info.
+ */
+static gpointer
+nns_construct_pipe_info (JNIEnv * env, jobject thiz, gpointer handle, const gchar * type)
+{
+  pipeline_info_s *pipe_info;
+
+  pipe_info = g_new0 (pipeline_info_s, 1);
+  g_assert (pipe_info);
+
+  pipe_info->pipeline_type = g_strdup (type);
+  pipe_info->pipeline_handle = handle;
+  pipe_info->element_handles = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, nns_free_element_data);
+  g_mutex_init (&pipe_info->lock);
+
+  (*env)->GetJavaVM (env, &pipe_info->jvm);
+  g_assert (pipe_info->jvm);
+  pthread_key_create (&pipe_info->jni_env, NULL);
+
+  pipe_info->version = (*env)->GetVersion (env);
+  pipe_info->instance = (*env)->NewGlobalRef (env, thiz);
+
+  jclass cls_data = (*env)->FindClass (env, "com/samsung/android/nnstreamer/TensorsData");
+  pipe_info->cls_tensors_data = (*env)->NewGlobalRef (env, cls_data);
+  (*env)->DeleteLocalRef (env, cls_data);
+
+  jclass cls_info = (*env)->FindClass (env, "com/samsung/android/nnstreamer/TensorsInfo");
+  pipe_info->cls_tensors_info = (*env)->NewGlobalRef (env, cls_info);
+  (*env)->DeleteLocalRef (env, cls_info);
+
+  return pipe_info;
+}
+
+/**
+ * @brief Destroy pipeline info.
+ */
+static void
+nns_destroy_pipe_info (pipeline_info_s * pipe_info, JNIEnv * env)
+{
+  g_assert (pipe_info);
+
+  if (g_str_equal (pipe_info->pipeline_type, NNS_PIPE_TYPE_PIPELINE)) {
+    ml_pipeline_destroy (pipe_info->pipeline_handle);
+  } else if (g_str_equal (pipe_info->pipeline_type, NNS_PIPE_TYPE_SINGLE)) {
+    ml_single_close (pipe_info->pipeline_handle);
+  } else {
+    nns_logw ("Given pipe type %s is unknown.", pipe_info->pipeline_type);
+    g_free (pipe_info->pipeline_handle);
+  }
+
+  g_mutex_lock (&pipe_info->lock);
+  g_hash_table_destroy (pipe_info->element_handles);
+  pipe_info->element_handles = NULL;
+  g_mutex_unlock (&pipe_info->lock);
+
+  g_mutex_clear (&pipe_info->lock);
+
+  (*env)->DeleteGlobalRef (env, pipe_info->instance);
+  (*env)->DeleteGlobalRef (env, pipe_info->cls_tensors_data);
+  (*env)->DeleteGlobalRef (env, pipe_info->cls_tensors_info);
+
+  g_free (pipe_info->pipeline_type);
+  g_free (pipe_info);
+}
+
+/**
+ * @brief Get element handle of given name.
+ */
+static gpointer
+nns_get_element_handle (pipeline_info_s * pipe_info, const gchar * name)
+{
+  element_data_s *item;
+
+  g_return_val_if_fail (pipe_info, NULL);
+  g_return_val_if_fail (name, NULL);
+
+  g_mutex_lock (&pipe_info->lock);
+  item = g_hash_table_lookup (pipe_info->element_handles, name);
+  g_mutex_unlock (&pipe_info->lock);
+
+  return (item) ? item->handle : NULL;
+}
+
+/**
+ * @brief Remove element handle of given name.
+ */
+static gboolean
+nns_remove_element_handle (pipeline_info_s * pipe_info, const gchar * name)
+{
+  gboolean ret;
+
+  g_return_val_if_fail (pipe_info, FALSE);
+  g_return_val_if_fail (name, FALSE);
+
+  g_mutex_lock (&pipe_info->lock);
+  ret = g_hash_table_remove (pipe_info->element_handles, name);
+  g_mutex_unlock (&pipe_info->lock);
+
+  return ret;
+}
+
+/**
+ * @brief Add new element handle of given name and type.
+ */
+static gboolean
+nns_add_element_handle (pipeline_info_s * pipe_info, const gchar * name,
+    element_data_s * item)
+{
+  gboolean ret;
+
+  g_return_val_if_fail (pipe_info, FALSE);
+  g_return_val_if_fail (name && item, FALSE);
+
+  g_mutex_lock (&pipe_info->lock);
+  ret = g_hash_table_insert (pipe_info->element_handles, g_strdup (name), item);
+  g_mutex_unlock (&pipe_info->lock);
+
+  return ret;
+}
+
+/**
+ * @brief Convert tensors data to TensorsData object.
+ */
+static gboolean
+nns_convert_tensors_data (pipeline_info_s * pipe_info, JNIEnv * env,
+    ml_tensors_data_s * data, jobject * result)
+{
+  guint i;
+
+  g_return_val_if_fail (pipe_info, FALSE);
+  g_return_val_if_fail (env, FALSE);
+  g_return_val_if_fail (data && result, FALSE);
+
+  /* method to generate tensors data */
+  jmethodID mid_init = (*env)->GetMethodID (env, pipe_info->cls_tensors_data, "<init>", "()V");
+  jmethodID mid_add = (*env)->GetMethodID (env, pipe_info->cls_tensors_data, "addTensorData", "(Ljava/lang/Object;)V");
+
+  jobject obj_data = (*env)->NewObject (env, pipe_info->cls_tensors_data, mid_init);
+  if (!obj_data) {
+    nns_loge ("Failed to allocate object for tensors data.");
+    goto done;
+  }
+
+  for (i = 0; i < data->num_tensors; i++) {
+    jobject item = (*env)->NewDirectByteBuffer (env, data->tensors[i].tensor,
+        (jlong) data->tensors[i].size);
+
+    (*env)->CallVoidMethod (env, obj_data, mid_add, item);
+    (*env)->DeleteLocalRef (env, item);
+  }
+
+done:
+  *result = obj_data;
+  return (obj_data != NULL);
+}
+
+/**
+ * @brief Parse tensors data from TensorsData object.
+ */
+static gboolean
+nns_parse_tensors_data (pipeline_info_s * pipe_info, JNIEnv * env,
+    jobject obj_data, ml_tensors_data_s * data)
+{
+  guint i;
+
+  g_return_val_if_fail (pipe_info, FALSE);
+  g_return_val_if_fail (env, FALSE);
+  g_return_val_if_fail (obj_data && data, FALSE);
+
+  /* get field 'mDataList' */
+  jfieldID fid_arraylist = (*env)->GetFieldID (env, pipe_info->cls_tensors_data, "mDataList", "java/util/ArrayList");
+  jobject obj_arraylist = (*env)->GetObjectField (env, obj_data, fid_arraylist);
+
+  /* method to get tensors data */
+  jclass cls_arraylist = (*env)->GetObjectClass (env, obj_arraylist);
+  jmethodID mid_size = (*env)->GetMethodID (env, cls_arraylist, "size", "()I");
+  jmethodID mid_get = (*env)->GetMethodID (env, cls_arraylist, "get", "(I)Ljava/lang/Object;");
+
+  /* number of tensors data */
+  data->num_tensors = (unsigned int) (*env)->CallIntMethod (env, obj_arraylist, mid_size);
+
+  /* set tensor data */
+  for (i = 0; i < data->num_tensors; i++) {
+    jobject tensor_data = (*env)->CallObjectMethod (env, obj_arraylist, mid_get, i);
+
+    if (tensor_data) {
+      size_t data_size = (size_t) (*env)->GetDirectBufferCapacity (env, tensor_data);
+      gpointer data_ptr = (*env)->GetDirectBufferAddress (env, tensor_data);
+
+      data->tensors[i].tensor = g_malloc (data_size);
+      memcpy (data->tensors[i].tensor, data_ptr, data_size);
+      data->tensors[i].size = data_size;
+
+      (*env)->DeleteLocalRef (env, tensor_data);
+    }
+
+    print_log ("Parse tensors data [%d] data at %p size %zd",
+        i, data->tensors[i].tensor, data->tensors[i].size);
+  }
+
+  (*env)->DeleteLocalRef (env, cls_arraylist);
+  (*env)->DeleteLocalRef (env, obj_arraylist);
+  return TRUE;
+}
+
+/**
+ * @brief Convert tensors info to TensorsInfo object.
+ */
+static gboolean
+nns_convert_tensors_info (pipeline_info_s * pipe_info, JNIEnv * env,
+    ml_tensors_info_s * info, jobject * result)
+{
+  guint i, j;
+
+  g_return_val_if_fail (pipe_info, FALSE);
+  g_return_val_if_fail (env, FALSE);
+  g_return_val_if_fail (info && result, FALSE);
+
+  /* method to generate tensors info */
+  jmethodID mid_init = (*env)->GetMethodID (env, pipe_info->cls_tensors_info, "<init>", "()V");
+  jmethodID mid_add = (*env)->GetMethodID (env, pipe_info->cls_tensors_info, "addTensorInfo", "(Ljava/lang/String;I[I)V");
+
+  jobject obj_info = (*env)->NewObject (env, pipe_info->cls_tensors_info, mid_init);
+  if (!obj_info) {
+    nns_loge ("Failed to allocate object for tensors info.");
+    goto done;
+  }
+
+  for (i = 0; i < info->num_tensors; i++) {
+    jstring name = NULL;
+    jint type;
+    jintArray dimension;
+
+    if (info->info[i].name)
+      name = (*env)->NewStringUTF (env, info->info[i].name);
+
+    type = (jint) info->info[i].type;
+
+    dimension = (*env)->NewIntArray (env, ML_TENSOR_RANK_LIMIT);
+
+    jint *dim = (*env)->GetIntArrayElements (env, dimension, NULL);
+    for (j = 0; j < ML_TENSOR_RANK_LIMIT; j++) {
+      dim[j] = (jint) info->info[i].dimension[j];
+    }
+    (*env)->ReleaseIntArrayElements (env, dimension, dim, 0);
+
+    (*env)->CallVoidMethod (env, obj_info, mid_add, name, type, dimension);
+
+    if (name)
+      (*env)->DeleteLocalRef (env, name);
+    (*env)->DeleteLocalRef (env, dimension);
+  }
+
+done:
+  *result = obj_info;
+  return (obj_info != NULL);
+}
+
+/**
+ * @brief Parse tensors info from TensorsInfo object.
+ */
+static gboolean
+nns_parse_tensors_info (pipeline_info_s * pipe_info, JNIEnv * env,
+    jobject obj_info, ml_tensors_info_s * info)
+{
+  guint i, j;
+
+  g_return_val_if_fail (pipe_info, FALSE);
+  g_return_val_if_fail (env, FALSE);
+  g_return_val_if_fail (obj_info && info, FALSE);
+
+  ml_tensors_info_initialize (info);
+
+  /* get field 'mInfoList' */
+  jfieldID fid_arraylist = (*env)->GetFieldID (env, pipe_info->cls_tensors_info, "mInfoList", "java/util/ArrayList");
+  jobject obj_arraylist = (*env)->GetObjectField (env, obj_info, fid_arraylist);
+
+  /* method to get tensors info */
+  jclass cls_arraylist = (*env)->GetObjectClass (env, obj_arraylist);
+  jmethodID mid_size = (*env)->GetMethodID (env, cls_arraylist, "size", "()I");
+  jmethodID mid_get = (*env)->GetMethodID (env, cls_arraylist, "get", "(I)Ljava/lang/Object;");
+
+  /* number of tensors info */
+  info->num_tensors = (unsigned int) (*env)->CallIntMethod (env, obj_arraylist, mid_size);
+
+  /* read tensor info */
+  for (i = 0; i < info->num_tensors; i++) {
+    jobject item = (*env)->CallObjectMethod (env, obj_arraylist, mid_get, i);
+    jclass cls_info = (*env)->GetObjectClass (env, item);
+
+    jmethodID mid_name = (*env)->GetMethodID (env, cls_info, "getName", "()Ljava/lang/String;");
+    jmethodID mid_type = (*env)->GetMethodID (env, cls_info, "getType", "()I");
+    jmethodID mid_dimension = (*env)->GetMethodID (env, cls_info, "getDimension", "()[I");
+
+    /* tensor name */
+    jstring name_str = (jstring) (*env)->CallObjectMethod (env, item, mid_name);
+    if (name_str) {
+      const gchar *name = (*env)->GetStringUTFChars (env, name_str, NULL);
+
+      info->info[i].name = g_strdup (name);
+      (*env)->ReleaseStringUTFChars (env, name_str, name);
+      (*env)->DeleteLocalRef (env, name_str);
+    }
+
+    /* tensor type */
+    info->info[i].type = (ml_tensor_type_e) (*env)->CallIntMethod (env, item, mid_type);
+
+    /* tensor dimension */
+    jintArray dim = (jintArray) (*env)->CallObjectMethod (env, item, mid_dimension);
+    jsize length = (*env)->GetArrayLength (env, dim);
+    jint *dimension = (*env)->GetIntArrayElements (env, dim, NULL);
+
+    g_assert (length == ML_TENSOR_RANK_LIMIT);
+    for (j = 0; j < length; j++) {
+      info->info[i].dimension[j] = (unsigned int) dimension[j];
+    }
+
+    (*env)->ReleaseIntArrayElements (env, dim, dimension, 0);
+    (*env)->DeleteLocalRef (env, dim);
+
+    (*env)->DeleteLocalRef (env, cls_info);
+    (*env)->DeleteLocalRef (env, item);
+
+    print_log ("Parse tensors info [%d] name %s type %d dim %d:%d:%d:%d",
+        i, GST_STR_NULL (info->info[i].name), info->info[i].type,
+        info->info[i].dimension[0], info->info[i].dimension[1],
+        info->info[i].dimension[2], info->info[i].dimension[3]);
+  }
+
+  (*env)->DeleteLocalRef (env, cls_arraylist);
+  (*env)->DeleteLocalRef (env, obj_arraylist);
+  return TRUE;
+}
+
+/**
+ * @brief Pipeline state change callback.
+ */
+static void
+nns_pipeline_state_cb (ml_pipeline_state_e state, void *user_data)
+{
+  pipeline_info_s *pipe_info;
+
+  pipe_info = (pipeline_info_s *) user_data;
+
+  JNIEnv *env = nns_get_jni_env (pipe_info);
+  if (env == NULL) {
+    nns_logw ("Cannot get jni env in the state callback.");
+    return;
+  }
+
+  jclass cls_pipeline = (*env)->GetObjectClass (env, pipe_info->instance);
+  jmethodID mid_callback = (*env)->GetMethodID (env, cls_pipeline, "stateChanged", "(I)V");
+  jint new_state = (jint) state;
+
+  (*env)->CallVoidMethod (env, pipe_info->instance, mid_callback, new_state);
+
+  if ((*env)->ExceptionCheck (env)) {
+    nns_loge ("Failed to call the callback method.");
+    (*env)->ExceptionClear (env);
+  }
+
+  (*env)->DeleteLocalRef (env, cls_pipeline);
+}
+
+/**
+ * @brief New data callback for sink node.
+ */
+static void
+nns_sink_data_cb (const ml_tensors_data_h data, const ml_tensors_info_h info, void *user_data)
+{
+  element_data_s *cb_data;
+  pipeline_info_s *pipe_info;
+  ml_tensors_data_s *out_data;
+  ml_tensors_info_s *out_info;
+
+  cb_data = (element_data_s *) user_data;
+  pipe_info = cb_data->pipe_info;
+  out_data = (ml_tensors_data_s *) data;
+  out_info = (ml_tensors_info_s *) info;
+
+  print_log ("Received new data from %s (total %d tensors)",
+      cb_data->name, out_data->num_tensors);
+
+  JNIEnv *env = nns_get_jni_env (pipe_info);
+  if (env == NULL) {
+    nns_logw ("Cannot get jni env in the sink callback.");
+    return;
+  }
+
+  jobject obj_data, obj_info;
+
+  obj_data = obj_info = NULL;
+  if (nns_convert_tensors_data (pipe_info, env, out_data, &obj_data) &&
+      nns_convert_tensors_info (pipe_info, env, out_info, &obj_info)) {
+    /* method for sink callback */
+    jclass cls_pipeline = (*env)->GetObjectClass (env, pipe_info->instance);
+    jmethodID mid_callback = (*env)->GetMethodID (env, cls_pipeline, "newDataReceived",
+        "(Ljava/lang/String;Lcom/samsung/android/nnstreamer/TensorsData;Lcom/samsung/android/nnstreamer/TensorsInfo;)V");
+    jstring sink_name = (*env)->NewStringUTF (env, cb_data->name);
+
+    (*env)->CallVoidMethod (env, pipe_info->instance, mid_callback, sink_name, obj_data, obj_info);
+
+    if ((*env)->ExceptionCheck (env)) {
+      nns_loge ("Failed to call the callback method.");
+      (*env)->ExceptionClear (env);
+    }
+
+    (*env)->DeleteLocalRef (env, sink_name);
+    (*env)->DeleteLocalRef (env, cls_pipeline);
+  } else {
+    nns_loge ("Failed to convert the result to data object.");
+  }
+
+  if (obj_data)
+    (*env)->DeleteLocalRef (env, obj_data);
+  if (obj_info)
+    (*env)->DeleteLocalRef (env, obj_info);
+}
+
+/**
+ * @brief Get sink handle.
+ */
+static void *
+nns_get_sink_handle (pipeline_info_s * pipe_info, const gchar * element_name)
+{
+  ml_pipeline_sink_h handle;
+  ml_pipeline_h pipe;
+  int status;
+
+  g_assert (pipe_info);
+  pipe = pipe_info->pipeline_handle;
+
+  handle = (ml_pipeline_sink_h) nns_get_element_handle (pipe_info, element_name);
+  if (handle == NULL) {
+    /* get sink handle and register to table */
+    element_data_s *item = g_new0 (element_data_s, 1);
+    g_assert (item);
+
+    status = ml_pipeline_sink_register (pipe, element_name, nns_sink_data_cb, item, &handle);
+    if (status != ML_ERROR_NONE) {
+      nns_loge ("Failed to get sink node %s.", element_name);
+      g_free (item);
+      return NULL;
+    }
+
+    item->name = g_strdup (element_name);
+    item->type = g_strdup (NNS_ELEMENT_TYPE_SINK);
+    item->handle = handle;
+    item->pipe_info = pipe_info;
+
+    if (!nns_add_element_handle (pipe_info, element_name, item)) {
+      nns_loge ("Failed to add sink node %s.", element_name);
+      nns_free_element_data (item);
+      return NULL;
+    }
+  }
+
+  return handle;
+}
+
+/**
+ * @brief Get src handle.
+ */
+static void *
+nns_get_src_handle (pipeline_info_s * pipe_info, const gchar * element_name)
+{
+  ml_pipeline_src_h handle;
+  ml_pipeline_h pipe;
+  int status;
+
+  g_assert (pipe_info);
+  pipe = pipe_info->pipeline_handle;
+
+  handle = (ml_pipeline_src_h) nns_get_element_handle (pipe_info, element_name);
+  if (handle == NULL) {
+    /* get src handle and register to table */
+    status = ml_pipeline_src_get_handle (pipe, element_name, &handle);
+    if (status != ML_ERROR_NONE) {
+      nns_loge ("Failed to get src node %s.", element_name);
+      return NULL;
+    }
+
+    element_data_s *item = g_new0 (element_data_s, 1);
+    g_assert (item);
+
+    item->name = g_strdup (element_name);
+    item->type = g_strdup (NNS_ELEMENT_TYPE_SRC);
+    item->handle = handle;
+    item->pipe_info = pipe_info;
+
+    if (!nns_add_element_handle (pipe_info, element_name, item)) {
+      nns_loge ("Failed to add src node %s.", element_name);
+      nns_free_element_data (item);
+      return NULL;
+    }
+  }
+
+  return handle;
+}
+
+/**
+ * @brief Get switch handle.
+ */
+static void *
+nns_get_switch_handle (pipeline_info_s * pipe_info, const gchar * element_name)
+{
+  ml_pipeline_switch_h handle;
+  ml_pipeline_switch_e switch_type;
+  ml_pipeline_h pipe;
+  int status;
+
+  g_assert (pipe_info);
+  pipe = pipe_info->pipeline_handle;
+
+  handle = (ml_pipeline_switch_h) nns_get_element_handle (pipe_info, element_name);
+  if (handle == NULL) {
+    /* get switch handle and register to table */
+    status = ml_pipeline_switch_get_handle (pipe, element_name, &switch_type, &handle);
+    if (status != ML_ERROR_NONE) {
+      nns_loge ("Failed to get switch %s.", element_name);
+      return NULL;
+    }
+
+    element_data_s *item = g_new0 (element_data_s, 1);
+    g_assert (item);
+
+    item->name = g_strdup (element_name);
+    if (switch_type == ML_PIPELINE_SWITCH_INPUT_SELECTOR)
+      item->type = g_strdup (NNS_ELEMENT_TYPE_SWITCH_IN);
+    else
+      item->type = g_strdup (NNS_ELEMENT_TYPE_SWITCH_OUT);
+    item->handle = handle;
+    item->pipe_info = pipe_info;
+
+    if (!nns_add_element_handle (pipe_info, element_name, item)) {
+      nns_loge ("Failed to add switch %s.", element_name);
+      nns_free_element_data (item);
+      return NULL;
+    }
+  }
+
+  return handle;
+}
+
+/**
+ * @brief Get valve handle.
+ */
+static void *
+nns_get_valve_handle (pipeline_info_s * pipe_info, const gchar * element_name)
+{
+  ml_pipeline_valve_h handle;
+  ml_pipeline_h pipe;
+  int status;
+
+  g_assert (pipe_info);
+  pipe = pipe_info->pipeline_handle;
+
+  handle = (ml_pipeline_valve_h) nns_get_element_handle (pipe_info, element_name);
+  if (handle == NULL) {
+    /* get valve handle and register to table */
+    status = ml_pipeline_valve_get_handle (pipe, element_name, &handle);
+    if (status != ML_ERROR_NONE) {
+      nns_loge ("Failed to get valve %s.", element_name);
+      return NULL;
+    }
+
+    element_data_s *item = g_new0 (element_data_s, 1);
+    g_assert (item);
+
+    item->name = g_strdup (element_name);
+    item->type = g_strdup (NNS_ELEMENT_TYPE_VALVE);
+    item->handle = handle;
+    item->pipe_info = pipe_info;
+
+    if (!nns_add_element_handle (pipe_info, element_name, item)) {
+      nns_loge ("Failed to add valve %s.", element_name);
+      nns_free_element_data (item);
+      return NULL;
+    }
+  }
+
+  return handle;
+}
+
+/**
+ * @brief Native method for single-shot API.
+ */
+jlong
+Java_com_samsung_android_nnstreamer_SingleShot_nativeOpen (JNIEnv * env, jobject thiz,
+    jstring model, jobject in, jobject out)
+{
+  pipeline_info_s *pipe_info = NULL;
+  ml_single_h single;
+  ml_tensors_info_h in_info, out_info;
+  int status;
+  const char *model_info = (*env)->GetStringUTFChars (env, model, NULL);
+
+  single = in_info = out_info = NULL;
+
+  if (in) {
+    ml_tensors_info_create (&in_info);
+    nns_parse_tensors_info (pipe_info, env, in, (ml_tensors_info_s *) in_info);
+  }
+
+  if (out) {
+    ml_tensors_info_create (&out_info);
+    nns_parse_tensors_info (pipe_info, env, out, (ml_tensors_info_s *) out_info);
+  }
+
+  /* supposed tensorflow-lite only for android */
+  status = ml_single_open (&single, model_info, in_info, out_info,
+      ML_NNFW_TYPE_ANY, ML_NNFW_HW_AUTO);
+  if (status != ML_ERROR_NONE) {
+    nns_loge ("Failed to create the pipeline.");
+    goto done;
+  }
+
+  pipe_info = nns_construct_pipe_info (env, thiz, single, NNS_PIPE_TYPE_SINGLE);
+
+done:
+  ml_tensors_info_destroy (in_info);
+  ml_tensors_info_destroy (out_info);
+
+  (*env)->ReleaseStringUTFChars (env, model, model_info);
+  return CAST_TO_LONG (pipe_info);
+}
+
+/**
+ * @brief Native method for single-shot API.
+ */
+void
+Java_com_samsung_android_nnstreamer_SingleShot_nativeClose (JNIEnv * env, jobject thiz,
+    jlong handle)
+{
+  pipeline_info_s *pipe_info;
+
+  pipe_info = CAST_TO_TYPE (handle, pipeline_info_s*);
+
+  nns_destroy_pipe_info (pipe_info, env);
+}
+
+/**
+ * @brief Native method for single-shot API.
+ */
+jobject
+Java_com_samsung_android_nnstreamer_SingleShot_nativeInvoke (JNIEnv * env, jobject thiz,
+    jlong handle, jobject in)
+{
+  pipeline_info_s *pipe_info;
+  ml_single_h single;
+  ml_tensors_data_s *input, *output;
+  int status;
+  jobject result = NULL;
+
+  pipe_info = CAST_TO_TYPE (handle, pipeline_info_s*);
+  single = pipe_info->pipeline_handle;
+  output = NULL;
+
+  input = g_new0 (ml_tensors_data_s, 1);
+  if (!input) {
+    nns_loge ("Failed to allocate memory for input data.");
+    goto done;
+  }
+
+  if (!nns_parse_tensors_data (pipe_info, env, in, input)) {
+    nns_loge ("Failed to parse input data.");
+    goto done;
+  }
+
+  status = ml_single_invoke (single, input, (ml_tensors_data_h *) &output);
+  if (status != ML_ERROR_NONE) {
+    nns_loge ("Failed to get the result from pipeline.");
+    goto done;
+  }
+
+  if (!nns_convert_tensors_data (pipe_info, env, output, &result)) {
+    nns_loge ("Failed to convert the result to data.");
+    result = NULL;
+  }
+
+done:
+  ml_tensors_data_destroy ((ml_tensors_data_h) input);
+  ml_tensors_data_destroy ((ml_tensors_data_h) output);
+  return result;
+}
+
+/**
+ * @brief Native method for single-shot API.
+ */
+jobject
+Java_com_samsung_android_nnstreamer_SingleShot_nativeGetInputInfo (JNIEnv * env, jobject thiz,
+    jlong handle)
+{
+  pipeline_info_s *pipe_info;
+  ml_single_h single;
+  ml_tensors_info_h info;
+  int status;
+  jobject result = NULL;
+
+  pipe_info = CAST_TO_TYPE (handle, pipeline_info_s*);
+  single = pipe_info->pipeline_handle;
+
+  status = ml_single_get_input_info (single, &info);
+  if (status != ML_ERROR_NONE) {
+    nns_loge ("Failed to get input info.");
+    goto done;
+  }
+
+  if (!nns_convert_tensors_info (pipe_info, env, info, &result)) {
+    nns_loge ("Failed to convert input info.");
+    result = NULL;
+  }
+
+done:
+  ml_tensors_info_destroy (info);
+  return result;
+}
+
+/**
+ * @brief Native method for single-shot API.
+ */
+jobject
+Java_com_samsung_android_nnstreamer_SingleShot_nativeGetOutputInfo (JNIEnv * env, jobject thiz,
+    jlong handle)
+{
+  pipeline_info_s *pipe_info;
+  ml_single_h single;
+  ml_tensors_info_h info;
+  int status;
+  jobject result = NULL;
+
+  pipe_info = CAST_TO_TYPE (handle, pipeline_info_s*);
+  single = pipe_info->pipeline_handle;
+
+  status = ml_single_get_output_info (single, &info);
+  if (status != ML_ERROR_NONE) {
+    nns_loge ("Failed to get output info.");
+    goto done;
+  }
+
+  if (!nns_convert_tensors_info (pipe_info, env, info, &result)) {
+    nns_loge ("Failed to convert output info.");
+    result = NULL;
+  }
+
+done:
+  ml_tensors_info_destroy (info);
+  return result;
+}
+
+/**
+ * @brief Native method for pipeline API.
+ */
+jlong
+Java_com_samsung_android_nnstreamer_Pipeline_nativeConstruct (JNIEnv * env, jobject thiz,
+    jstring description, jboolean add_state_cb)
+{
+  pipeline_info_s *pipe_info = NULL;
+  ml_pipeline_h pipe;
+  int status;
+  const char *pipeline = (*env)->GetStringUTFChars (env, description, NULL);
+
+  print_log ("Pipeline: %s", pipeline);
+  pipe_info = nns_construct_pipe_info (env, thiz, NULL, NNS_PIPE_TYPE_PIPELINE);
+
+  if (add_state_cb)
+    status = ml_pipeline_construct (pipeline, nns_pipeline_state_cb, pipe_info, &pipe);
+  else
+    status = ml_pipeline_construct (pipeline, NULL, NULL, &pipe);
+
+  if (status != ML_ERROR_NONE) {
+    nns_loge ("Failed to create the pipeline.");
+    nns_destroy_pipe_info (pipe_info, env);
+    pipe_info = NULL;
+  } else {
+    pipe_info->pipeline_handle = pipe;
+  }
+
+  (*env)->ReleaseStringUTFChars (env, description, pipeline);
+  return CAST_TO_LONG (pipe_info);
+}
+
+/**
+ * @brief Native method for pipeline API.
+ */
+void
+Java_com_samsung_android_nnstreamer_Pipeline_nativeDestroy (JNIEnv * env, jobject thiz,
+    jlong handle)
+{
+  pipeline_info_s *pipe_info = NULL;
+
+  pipe_info = CAST_TO_TYPE (handle, pipeline_info_s*);
+
+  nns_destroy_pipe_info (pipe_info, env);
+}
+
+/**
+ * @brief Native method for pipeline API.
+ */
+jboolean
+Java_com_samsung_android_nnstreamer_Pipeline_nativeStart (JNIEnv * env, jobject thiz,
+    jlong handle)
+{
+  pipeline_info_s *pipe_info = NULL;
+  ml_pipeline_h pipe;
+  int status;
+
+  pipe_info = CAST_TO_TYPE (handle, pipeline_info_s*);
+  pipe = pipe_info->pipeline_handle;
+
+  status = ml_pipeline_start (pipe);
+  if (status != ML_ERROR_NONE) {
+    nns_loge ("Failed to start the pipeline.");
+    return JNI_FALSE;
+  }
+
+  return JNI_TRUE;
+}
+
+/**
+ * @brief Native method for pipeline API.
+ */
+jboolean
+Java_com_samsung_android_nnstreamer_Pipeline_nativeStop (JNIEnv * env, jobject thiz,
+    jlong handle)
+{
+  pipeline_info_s *pipe_info = NULL;
+  ml_pipeline_h pipe;
+  int status;
+
+  pipe_info = CAST_TO_TYPE (handle, pipeline_info_s*);
+  pipe = pipe_info->pipeline_handle;
+
+  status = ml_pipeline_stop (pipe);
+  if (status != ML_ERROR_NONE) {
+    nns_loge ("Failed to stop the pipeline.");
+    return JNI_FALSE;
+  }
+
+  return JNI_TRUE;
+}
+
+/**
+ * @brief Native method for pipeline API.
+ */
+jint
+Java_com_samsung_android_nnstreamer_Pipeline_nativeGetState (JNIEnv * env, jobject thiz,
+    jlong handle)
+{
+  pipeline_info_s *pipe_info = NULL;
+  ml_pipeline_h pipe;
+  ml_pipeline_state_e state;
+  int status;
+
+  pipe_info = CAST_TO_TYPE (handle, pipeline_info_s*);
+  pipe = pipe_info->pipeline_handle;
+
+  status = ml_pipeline_get_state (pipe, &state);
+  if (status != ML_ERROR_NONE) {
+    nns_loge ("Failed to get the pipeline state.");
+    state = ML_PIPELINE_STATE_UNKNOWN;
+  }
+
+  return (jint) state;
+}
+
+/**
+ * @brief Native method for pipeline API.
+ */
+jboolean
+Java_com_samsung_android_nnstreamer_Pipeline_nativeInputData (JNIEnv * env, jobject thiz,
+    jlong handle, jstring name, jobject in)
+{
+  pipeline_info_s *pipe_info = NULL;
+  ml_pipeline_src_h src;
+  ml_tensors_data_s *input = NULL;
+  int status;
+  jboolean res = JNI_FALSE;
+  const char *element_name = (*env)->GetStringUTFChars (env, name, NULL);
+
+  pipe_info = CAST_TO_TYPE (handle, pipeline_info_s*);
+
+  src = (ml_pipeline_src_h) nns_get_src_handle (pipe_info, element_name);
+  if (src == NULL) {
+    goto done;
+  }
+
+  input = g_new0 (ml_tensors_data_s, 1);
+  if (!input) {
+    nns_loge ("Failed to allocate memory for input data.");
+    goto done;
+  }
+
+  if (!nns_parse_tensors_data (pipe_info, env, in, input)) {
+    nns_loge ("Failed to parse input data.");
+    ml_tensors_data_destroy ((ml_tensors_data_h) input);
+    goto done;
+  }
+
+  status = ml_pipeline_src_input_data (src, (ml_tensors_data_h) input,
+      ML_PIPELINE_BUF_POLICY_AUTO_FREE);
+  if (status != ML_ERROR_NONE) {
+    nns_loge ("Failed to input tensors data.");
+    goto done;
+  }
+
+  res = JNI_TRUE;
+
+done:
+  (*env)->ReleaseStringUTFChars (env, name, element_name);
+  return res;
+}
+
+/**
+ * @brief Native method for pipeline API.
+ */
+jobjectArray
+Java_com_samsung_android_nnstreamer_Pipeline_nativeGetSwitchPads (JNIEnv * env, jobject thiz,
+    jlong handle, jstring name)
+{
+  pipeline_info_s *pipe_info = NULL;
+  ml_pipeline_switch_h node;
+  int status;
+  const char *element_name = (*env)->GetStringUTFChars (env, name, NULL);
+  char **pad_list = NULL;
+  guint i, total = 0;
+  jobjectArray result = NULL;
+
+  pipe_info = CAST_TO_TYPE (handle, pipeline_info_s*);
+
+  node = (ml_pipeline_switch_h) nns_get_switch_handle (pipe_info, element_name);
+  if (node == NULL) {
+    goto done;
+  }
+
+  status = ml_pipeline_switch_get_pad_list (node, &pad_list);
+  if (status != ML_ERROR_NONE) {
+    nns_loge ("Failed to get the pad list of switch %s.", element_name);
+    goto done;
+  }
+
+  /* set string array */
+  if (pad_list) {
+    jclass cls_string = (*env)->FindClass (env, "java/lang/String");
+
+    while (pad_list[total] != NULL)
+      total++;
+
+    result = (*env)->NewObjectArray (env, total, cls_string, (*env)->NewStringUTF (env, ""));
+    if (result == NULL) {
+      nns_loge ("Failed to allocate string array.");
+      goto done;
+    }
+
+    for (i = 0; i < total; i++) {
+      (*env)->SetObjectArrayElement (env, result, i, (*env)->NewStringUTF (env, pad_list[i]));
+      g_free (pad_list[i]);
+    }
+
+    g_free (pad_list);
+    pad_list = NULL;
+
+    (*env)->DeleteLocalRef (env, cls_string);
+  }
+
+done:
+  /* free pad list */
+  if (pad_list) {
+    i = 0;
+    while (pad_list[i] != NULL) {
+      g_free (pad_list[i]);
+    }
+    g_free (pad_list);
+  }
+
+  (*env)->ReleaseStringUTFChars (env, name, element_name);
+  return result;
+}
+
+/**
+ * @brief Native method for pipeline API.
+ */
+jboolean
+Java_com_samsung_android_nnstreamer_Pipeline_nativeSelectSwitchPad (JNIEnv * env, jobject thiz,
+    jlong handle, jstring name, jstring pad)
+{
+  pipeline_info_s *pipe_info = NULL;
+  ml_pipeline_switch_h node;
+  int status;
+  jboolean res = JNI_FALSE;
+  const char *element_name = (*env)->GetStringUTFChars (env, name, NULL);
+  const char *pad_name = (*env)->GetStringUTFChars (env, pad, NULL);
+
+  pipe_info = CAST_TO_TYPE (handle, pipeline_info_s*);
+
+  node = (ml_pipeline_switch_h) nns_get_switch_handle (pipe_info, element_name);
+  if (node == NULL) {
+    goto done;
+  }
+
+  status = ml_pipeline_switch_select (node, pad_name);
+  if (status != ML_ERROR_NONE) {
+    nns_loge ("Failed to select switch pad %s.", pad_name);
+    goto done;
+  }
+
+  res = JNI_TRUE;
+
+done:
+  (*env)->ReleaseStringUTFChars (env, name, element_name);
+  (*env)->ReleaseStringUTFChars (env, pad, pad_name);
+  return res;
+}
+
+/**
+ * @brief Native method for pipeline API.
+ */
+jboolean
+Java_com_samsung_android_nnstreamer_Pipeline_nativeControlValve (JNIEnv * env, jobject thiz,
+    jlong handle, jstring name, jboolean open)
+{
+  pipeline_info_s *pipe_info = NULL;
+  ml_pipeline_valve_h node;
+  int status;
+  jboolean res = JNI_FALSE;
+  const char *element_name = (*env)->GetStringUTFChars (env, name, NULL);
+
+  pipe_info = CAST_TO_TYPE (handle, pipeline_info_s*);
+
+  node = (ml_pipeline_valve_h) nns_get_valve_handle (pipe_info, element_name);
+  if (node == NULL) {
+    goto done;
+  }
+
+  status = ml_pipeline_valve_set_open (node, (open == JNI_TRUE));
+  if (status != ML_ERROR_NONE) {
+    nns_loge ("Failed to control valve %s.", element_name);
+    goto done;
+  }
+
+  res = JNI_TRUE;
+
+done:
+  (*env)->ReleaseStringUTFChars (env, name, element_name);
+  return res;
+}
+
+/**
+ * @brief Native method for pipeline API.
+ */
+jboolean
+Java_com_samsung_android_nnstreamer_Pipeline_nativeAddSinkCallback (JNIEnv * env, jobject thiz,
+    jlong handle, jstring name)
+{
+  pipeline_info_s *pipe_info = NULL;
+  ml_pipeline_sink_h sink;
+  jboolean res = JNI_FALSE;
+  const char *element_name = (*env)->GetStringUTFChars (env, name, NULL);
+
+  pipe_info = CAST_TO_TYPE (handle, pipeline_info_s*);
+
+  sink = (ml_pipeline_sink_h) nns_get_sink_handle (pipe_info, element_name);
+  if (sink == NULL) {
+    goto done;
+  }
+
+  res = JNI_TRUE;
+
+done:
+  (*env)->ReleaseStringUTFChars (env, name, element_name);
+  return res;
+}
+
+/**
+ * @brief Native method for pipeline API.
+ */
+jboolean
+Java_com_samsung_android_nnstreamer_Pipeline_nativeRemoveSinkCallback (JNIEnv * env, jobject thiz,
+    jlong handle, jstring name)
+{
+  pipeline_info_s *pipe_info = NULL;
+  ml_pipeline_sink_h sink;
+  jboolean res = JNI_FALSE;
+  const char *element_name = (*env)->GetStringUTFChars (env, name, NULL);
+
+  pipe_info = CAST_TO_TYPE (handle, pipeline_info_s*);
+
+  /* get handle from table */
+  sink = (ml_pipeline_sink_h) nns_get_element_handle (pipe_info, element_name);
+  if (sink) {
+    nns_remove_element_handle (pipe_info, element_name);
+    res = JNI_TRUE;
+  }
+
+  (*env)->ReleaseStringUTFChars (env, name, element_name);
+  return res;
+}
+
+/**
+ * @brief Native method to initialize NNStreamer.
+ */
+jboolean
+Java_com_samsung_android_nnstreamer_NNStreamer_nativeInitialize (JNIEnv * env, jclass clazz,
+    jobject context)
+{
+  nns_logi ("Called native initialize.");
+
+  if (!gst_is_initialized ()) {
+    nns_loge ("GStreamer is not initialized.");
+    return JNI_FALSE;
+  }
+
+  /* register nnstreamer plugins */
+  GST_PLUGIN_STATIC_REGISTER (nnstreamer);
+
+  /* filter tensorflow-lite sub-plugin */
+  init_filter_tflite ();
+
+  /* decoder sub-plugins */
+  init_dv ();
+  init_bb ();
+  init_il ();
+  init_pose ();
+
+  return JNI_TRUE;
+}
+
+/**
+ * @brief Native method to get the version string of NNStreamer and GStreamer.
+ */
+jstring
+Java_com_samsung_android_nnstreamer_NNStreamer_nativeGetVersion (JNIEnv * env, jclass clazz)
+{
+  gchar *gst_ver = gst_version_string ();
+  gchar *version_str = g_strdup_printf ("NNStreamer %s, %s", VERSION, gst_ver);
+
+  jstring version = (*env)->NewStringUTF (env, version_str);
+
+  g_free (gst_ver);
+  g_free (version_str);
+  return version;
+}
diff --git a/api/android/api/proguard-rules.pro b/api/android/api/proguard-rules.pro
new file mode 100644 (file)
index 0000000..f1b4245
--- /dev/null
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/api/android/api/res/values/strings.xml b/api/android/api/res/values/strings.xml
new file mode 100644 (file)
index 0000000..4f213e1
--- /dev/null
@@ -0,0 +1,3 @@
+<resources>
+    <string name="lib_name">NNStreamer API</string>
+</resources>
diff --git a/api/android/api/src/com/samsung/android/nnstreamer/NNStreamer.java b/api/android/api/src/com/samsung/android/nnstreamer/NNStreamer.java
new file mode 100644 (file)
index 0000000..dabe029
--- /dev/null
@@ -0,0 +1,136 @@
+package com.samsung.android.nnstreamer;
+
+import android.content.Context;
+
+import org.freedesktop.gstreamer.GStreamer;
+
+/**
+ * Defines the types and limits in NNStreamer.<br>
+ * To use NNStreamer, an application should call {@link #initialize(Context)} with its context.<br>
+ * <br>
+ * NNStreamer is a set of GStreamer plugins that allow GStreamer developers to adopt neural network models easily and efficiently
+ * and neural network developers to manage stream pipelines and their filters easily and efficiently.<br>
+ * <br>
+ * See <a href="https://github.com/nnsuite/nnstreamer">https://github.com/nnsuite/nnstreamer</a> for the details.
+ */
+public final class NNStreamer {
+    /**
+     * The maximum rank that NNStreamer supports.
+     */
+    public static final int TENSOR_RANK_LIMIT = 4;
+
+    /**
+     * The maximum number of tensor instances that tensors may have.
+     */
+    public static final int TENSOR_SIZE_LIMIT = 16;
+
+    /**
+     * The data type of tensor in NNStreamer: Integer 32bit.
+     */
+    public static final int TENSOR_TYPE_INT32 = 0;
+
+    /**
+     * The data type of tensor in NNStreamer: Unsigned integer 32bit.
+     */
+    public static final int TENSOR_TYPE_UINT32 = 1;
+
+    /**
+     * The data type of tensor in NNStreamer: Integer 16bit.
+     */
+    public static final int TENSOR_TYPE_INT16 = 2;
+
+    /**
+     * The data type of tensor in NNStreamer: Unsigned integer 16bit.
+     */
+    public static final int TENSOR_TYPE_UINT16 = 3;
+
+    /**
+     * The data type of tensor in NNStreamer: Integer 8bit.
+     */
+    public static final int TENSOR_TYPE_INT8 = 4;
+
+    /**
+     * The data type of tensor in NNStreamer: Unsigned integer 8bit.
+     */
+    public static final int TENSOR_TYPE_UINT8 = 5;
+
+    /**
+     * The data type of tensor in NNStreamer: Float 64bit.
+     */
+    public static final int TENSOR_TYPE_FLOAT64 = 6;
+
+    /**
+     * The data type of tensor in NNStreamer: Float 32bit.
+     */
+    public static final int TENSOR_TYPE_FLOAT32 = 7;
+
+    /**
+     * The data type of tensor in NNStreamer: Integer 64bit.
+     */
+    public static final int TENSOR_TYPE_INT64 = 8;
+
+    /**
+     * The data type of tensor in NNStreamer: Unsigned integer 64bit.
+     */
+    public static final int TENSOR_TYPE_UINT64 = 9;
+
+    /**
+     * Unknown data type of tensor in NNStreamer.
+     */
+    public static final int TENSOR_TYPE_UNKNOWN = 10;
+
+    /**
+     * The state of pipeline: Unknown state.
+     */
+    public static final int PIPELINE_STATE_UNKNOWN = 0;
+
+    /**
+     * The state of pipeline: Initial state of the pipeline.
+     */
+    public static final int PIPELINE_STATE_NULL = 1;
+
+    /**
+     * The state of pipeline: The pipeline is ready to go to PAUSED.
+     */
+    public static final int PIPELINE_STATE_READY = 2;
+
+    /**
+     * The state of pipeline: The pipeline is stopped, ready to accept and process data.
+     */
+    public static final int PIPELINE_STATE_PAUSED = 3;
+
+    /**
+     * The state of pipeline: The pipeline is started and the data is flowing.
+     */
+    public static final int PIPELINE_STATE_PLAYING = 4;
+
+    private static native boolean nativeInitialize(Context context);
+    private static native String nativeGetVersion();
+
+    /**
+     * Initializes GStreamer and NNStreamer, registering the plugins and loading necessary libraries.
+     *
+     * @param context The application context
+     *
+     * @return true if successfully initialized
+     */
+    public static boolean initialize(Context context) {
+        try {
+            System.loadLibrary("nnstreamer-native");
+            GStreamer.init(context);
+            return nativeInitialize(context);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * Gets the version string of GStreamer and NNStreamer.
+     *
+     * @return The version string
+     */
+    public static String getVersion() {
+        return nativeGetVersion();
+    }
+}
diff --git a/api/android/api/src/com/samsung/android/nnstreamer/Pipeline.java b/api/android/api/src/com/samsung/android/nnstreamer/Pipeline.java
new file mode 100644 (file)
index 0000000..8f8314c
--- /dev/null
@@ -0,0 +1,369 @@
+package com.samsung.android.nnstreamer;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.Hashtable;
+
+/**
+ * Provides interfaces to create and execute stream pipelines with neural networks.<br>
+ * <br>
+ * <code>Pipeline</code> allows the following operations with NNStreamer:<br>
+ * - Create a stream pipeline with NNStreamer plugins, GStreamer plugins.<br>
+ * - Interfaces to push data to the pipeline from the application.<br>
+ * - Interfaces to pull data from the pipeline to the application.<br>
+ * - Interfaces to start/stop/destroy the pipeline.<br>
+ * - Interfaces to control switches and valves in the pipeline.<br>
+ */
+public final class Pipeline implements AutoCloseable {
+    private long mHandle = 0;
+    private Hashtable<String, NewDataCallback> mSinkCallbacks = new Hashtable<>();
+    private StateChangeCallback mStateCallback = null;
+
+    private native long nativeConstruct(String description, boolean addStateCb);
+    private native void nativeDestroy(long handle);
+    private native boolean nativeStart(long handle);
+    private native boolean nativeStop(long handle);
+    private native int nativeGetState(long handle);
+    private native boolean nativeInputData(long handle, String name, TensorsData data);
+    private native String[] nativeGetSwitchPads(long handle, String name);
+    private native boolean nativeSelectSwitchPad(long handle, String name, String pad);
+    private native boolean nativeControlValve(long handle, String name, boolean open);
+    private native boolean nativeAddSinkCallback(long handle, String name);
+    private native boolean nativeRemoveSinkCallback(long handle, String name);
+
+    /**
+     * Interface definition for a callback to be invoked when a sink node receives new data.
+     */
+    public interface NewDataCallback {
+        /**
+         * Called when a sink node receives new data.
+         *
+         * If an application wants to accept data outputs of an NNStreamer stream, use this callback to get data from the stream.
+         * Note that the buffer may be deallocated after the return and this is synchronously called.
+         * Thus, if you need the data afterwards, copy the data to another buffer and return fast.
+         * Do not spend too much time in the callback. It is recommended to use very small tensors at sinks.
+         *
+         * @param data The output data (a single frame, tensor/tensors)
+         * @param info The tensors information (dimension, type of output tensor/tensors)
+         */
+        void onNewDataReceived(TensorsData data, TensorsInfo info);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the pipeline state is changed.
+     * This callback can be registered only when constructing the pipeline.<br>
+     * <br>
+     * The state of pipeline:<br>
+     * {@link NNStreamer#PIPELINE_STATE_UNKNOWN}<br>
+     * {@link NNStreamer#PIPELINE_STATE_NULL}<br>
+     * {@link NNStreamer#PIPELINE_STATE_READY}<br>
+     * {@link NNStreamer#PIPELINE_STATE_PAUSED}<br>
+     * {@link NNStreamer#PIPELINE_STATE_PLAYING}<br>
+     *
+     * @see #start()
+     * @see #stop()
+     */
+    public interface StateChangeCallback {
+        /**
+         * Called when the pipeline state is changed.
+         *
+         * If an application wants to get the change of pipeline state, use this callback.
+         * This callback can be registered when constructing the pipeline.
+         * This is synchronously called, so do not spend too much time in the callback.
+         *
+         * @param state The changed state
+         */
+        void onStateChanged(int state);
+    }
+
+    /**
+     * Creates a new <code>Pipeline</code> instance with the given pipeline description.
+     *
+     * @param description The pipeline description.
+     *                    Refer to GStreamer manual or NNStreamer (github.com/nnsuite/nnstreamer) documentation for examples and the grammar.
+     *
+     * @throws IllegalArgumentException if given param is null
+     * @throws IllegalStateException if failed to construct the pipeline
+     */
+    public Pipeline(@NonNull String description) {
+        this(description, null);
+    }
+
+    /**
+     * Creates a new <code>Pipeline</code> instance with the given pipeline description.
+     *
+     * @param description The pipeline description.
+     *                    Refer to GStreamer manual or NNStreamer (github.com/nnsuite/nnstreamer) documentation for examples and the grammar.
+     * @param callback    The function to be called when the pipeline state is changed.
+     *                    You may set null if it's not required.
+     *
+     * @throws IllegalArgumentException if given param is null
+     * @throws IllegalStateException if failed to construct the pipeline
+     */
+    public Pipeline(@NonNull String description, @Nullable StateChangeCallback callback) {
+        if (description == null) {
+            throw new IllegalArgumentException("The param description is null");
+        }
+
+        mHandle = nativeConstruct(description, (callback != null));
+        if (mHandle == 0) {
+            throw new IllegalStateException("Failed to construct the pipeline");
+        }
+
+        mStateCallback = callback;
+    }
+
+    /**
+     * Starts the pipeline, asynchronously.
+     * The pipeline state would be changed to 'PLAYING'.
+     * If you need to get the changed state, add a callback while constructing a pipeline.
+     *
+     * @throws IllegalStateException if failed to start the pipeline
+     *
+     * @see StateChangeCallback
+     */
+    public void start() {
+        checkPipelineHandle();
+
+        if (!nativeStart(mHandle)) {
+            throw new IllegalStateException("Failed to start the pipeline");
+        }
+    }
+
+    /**
+     * Stops the pipeline, asynchronously.
+     * The pipeline state would be changed to 'PAUSED'.
+     * If you need to get the changed state, add a callback while constructing a pipeline.
+     *
+     * @throws IllegalStateException if failed to stop the pipeline
+     *
+     * @see StateChangeCallback
+     */
+    public void stop() {
+        checkPipelineHandle();
+
+        if (!nativeStop(mHandle)) {
+            throw new IllegalStateException("Failed to stop the pipeline");
+        }
+    }
+
+    /**
+     * Gets the state of pipeline.<br>
+     * {@link NNStreamer#PIPELINE_STATE_UNKNOWN}<br>
+     * {@link NNStreamer#PIPELINE_STATE_NULL}<br>
+     * {@link NNStreamer#PIPELINE_STATE_READY}<br>
+     * {@link NNStreamer#PIPELINE_STATE_PAUSED}<br>
+     * {@link NNStreamer#PIPELINE_STATE_PLAYING}<br>
+     *
+     * @return The state of pipeline
+     *
+     * @throws IllegalStateException if the pipeline is not constructed
+     *
+     * @see StateChangeCallback
+     */
+    public int getState() {
+        checkPipelineHandle();
+
+        return nativeGetState(mHandle);
+    }
+
+    /**
+     * Adds an input data frame to source node.
+     *
+     * @param name The name of source node
+     * @param data The input data (a single frame, tensor/tensors)
+     *
+     * @throws IllegalArgumentException if given param is null
+     * @throws IllegalStateException if failed to push data to source node
+     */
+    public void inputData(@NonNull String name, @NonNull TensorsData data) {
+        checkPipelineHandle();
+
+        if (name == null) {
+            throw new IllegalArgumentException("The param name is null");
+        }
+
+        if (data == null) {
+            throw new IllegalArgumentException("The param data is null");
+        }
+
+        if (!nativeInputData(mHandle, name, data)) {
+            throw new IllegalStateException("Failed to push data to source node " + name);
+        }
+    }
+
+    /**
+     * Gets the pad names of a switch.
+     *
+     * @param name The name of switch node
+     *
+     * @return The list of pad names
+     *
+     * @throws IllegalArgumentException if given param is null
+     * @throws IllegalStateException if failed to get the list of pad names
+     */
+    public String[] getSwitchPads(@NonNull String name) {
+        checkPipelineHandle();
+
+        if (name == null) {
+            throw new IllegalArgumentException("The param name is null");
+        }
+
+        String[] pads = nativeGetSwitchPads(mHandle, name);
+
+        if (pads == null || pads.length == 0) {
+            throw new IllegalStateException("Failed to get the pads in switch " + name);
+        }
+
+        return pads;
+    }
+
+    /**
+     * Controls the switch to select input/output nodes(pads).
+     *
+     * @param name The name of switch node
+     * @param pad  The name of the chosen pad to be activated
+     *
+     * @throws IllegalArgumentException if given param is null
+     * @throws IllegalStateException if failed to select the switch pad
+     */
+    public void selectSwitchPad(@NonNull String name, @NonNull String pad) {
+        checkPipelineHandle();
+
+        if (name == null) {
+            throw new IllegalArgumentException("The param name is null");
+        }
+
+        if (pad == null) {
+            throw new IllegalArgumentException("The param pad is null");
+        }
+
+        if (!nativeSelectSwitchPad(mHandle, name, pad)) {
+            throw new IllegalStateException("Failed to select the pad " + pad);
+        }
+    }
+
+    /**
+     * Controls the valve.
+     * Set the flag true to open(let the flow pass), false to close(drop & stop the flow).
+     *
+     * @param name The name of valve node
+     * @param open The flag to control the flow
+     *
+     * @throws IllegalArgumentException if given param is null
+     * @throws IllegalStateException if failed to change the valve state
+     */
+    public void controlValve(@NonNull String name, boolean open) {
+        checkPipelineHandle();
+
+        if (name == null) {
+            throw new IllegalArgumentException("The param name is null");
+        }
+
+        if (!nativeControlValve(mHandle, name, open)) {
+            throw new IllegalStateException("Failed to change the valve " + name);
+        }
+    }
+
+    /**
+     * Registers new data callback to sink node.
+     * If an application registers a callback with same name, the callback is replaced with new one.
+     *
+     * @param name     The name of sink node
+     * @param callback Callback for new data
+     *
+     * @throws IllegalArgumentException if given param is null
+     * @throws IllegalStateException if failed to add the callback to sink node in the pipeline
+     */
+    public void setSinkCallback(@NonNull String name, NewDataCallback callback) {
+        if (name == null) {
+            throw new IllegalArgumentException("The param name is null");
+        }
+
+        synchronized(this) {
+            if (mSinkCallbacks.containsKey(name)) {
+                if (callback == null) {
+                    /* remove callback */
+                    mSinkCallbacks.remove(name);
+                    nativeRemoveSinkCallback(mHandle, name);
+                } else {
+                    mSinkCallbacks.replace(name, callback);
+                }
+            } else {
+                if (callback == null) {
+                    throw new IllegalArgumentException("The param callback is null");
+                } else {
+                    if (nativeAddSinkCallback(mHandle, name)) {
+                        mSinkCallbacks.put(name, callback);
+                    } else {
+                        throw new IllegalStateException("Failed to set sink callback to " + name);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Internal method called from native when a new data is available.
+     */
+    private void newDataReceived(String name, TensorsData data, TensorsInfo info) {
+        NewDataCallback cb;
+
+        synchronized(this) {
+            cb = mSinkCallbacks.get(name);
+        }
+
+        if (cb != null) {
+            cb.onNewDataReceived(data, info);
+        }
+    }
+
+    /**
+     * Internal method called from native when the state of pipeline is changed.
+     */
+    private void stateChanged(int state) {
+        StateChangeCallback cb;
+
+        synchronized(this) {
+            cb = mStateCallback;
+        }
+
+        if (cb != null) {
+            cb.onStateChanged(state);
+        }
+    }
+
+    /**
+     * Internal method to check native handle.
+     *
+     * @throws IllegalStateException if the pipeline is not constructed
+     */
+    private void checkPipelineHandle() {
+        if (mHandle == 0) {
+            throw new IllegalStateException("The pipeline is not constructed");
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    @Override
+    public void close() {
+        synchronized(this) {
+            mSinkCallbacks.clear();
+            mStateCallback = null;
+        }
+
+        if (mHandle > 0) {
+            nativeDestroy(mHandle);
+            mHandle = 0;
+        }
+    }
+}
diff --git a/api/android/api/src/com/samsung/android/nnstreamer/SingleShot.java b/api/android/api/src/com/samsung/android/nnstreamer/SingleShot.java
new file mode 100644 (file)
index 0000000..aff76cd
--- /dev/null
@@ -0,0 +1,158 @@
+package com.samsung.android.nnstreamer;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.io.File;
+
+/**
+ * Provides interfaces to invoke a neural network model with a single instance of input data.<br>
+ * This function is a syntactic sugar of NNStreamer Pipeline API with simplified features;
+ * thus, users are supposed to use NNStreamer Pipeline API directly if they want more advanced features.<br>
+ * The user is expected to preprocess the input data for the given neural network model.<br>
+ * <br>
+ * <code>SingleShot</code> allows the following operations with NNStreamer:<br>
+ * - Open a machine learning model.<br>
+ * - Interfaces to enter a single instance of input data to the opened model.<br>
+ * - Utility functions to get the information of opened model.<br>
+ */
+public final class SingleShot implements AutoCloseable {
+    private long mHandle = 0;
+
+    private native long nativeOpen(String model, TensorsInfo in, TensorsInfo out);
+    private native void nativeClose(long handle);
+    private native TensorsData nativeInvoke(long handle, TensorsData in);
+    private native TensorsInfo nativeGetInputInfo(long handle);
+    private native TensorsInfo nativeGetOutputInfo(long handle);
+
+    /**
+     * Creates a new <code>SingleShot</code> instance with the given model.
+     * If the model has flexible data dimensions, the pipeline will not be constructed and this will make an exception.
+     *
+     * @param model The path to the neural network model file
+     *
+     * @throws IllegalArgumentException if given param is null
+     * @throws IllegalStateException if failed to construct the pipeline
+     */
+    public SingleShot(@NonNull File model) {
+        this(model, null, null);
+    }
+
+    /**
+     * Creates a new <code>SingleShot</code> instance with the given model.
+     * The input and output tensors information are required if the given model has flexible data dimensions,
+     * where the information MUST be given before executing the model.
+     * However, once it's given, the dimension cannot be changed for the given model handle.
+     * You may set null if it's not required.
+     *
+     * @param model The path to the neural network model file
+     * @param in    The input tensors information
+     * @param out   The output tensors information
+     *
+     * @throws IllegalArgumentException if given param is null
+     * @throws IllegalStateException if failed to construct the pipeline
+     */
+    public SingleShot(@NonNull File model, @Nullable TensorsInfo in, @Nullable TensorsInfo out) {
+        if (model == null) {
+            throw new IllegalArgumentException("The param model is null");
+        }
+
+        String path = model.getAbsolutePath();
+
+        mHandle = nativeOpen(path, in, out);
+        if (mHandle == 0) {
+            throw new IllegalStateException("Failed to construct the pipeline");
+        }
+    }
+
+    /**
+     * Invokes the model with the given input data.
+     * Even if the model has flexible input data dimensions,
+     * input data frames of an instance of a model should share the same dimension.
+     *
+     * @param in The input data to be inferred (a single frame, tensor/tensors)
+     *
+     * @return The output data (a single frame, tensor/tensors)
+     *
+     * @throws IllegalStateException if failed to invoke the model
+     * @throws IllegalArgumentException if given param is null
+     */
+    public TensorsData invoke(@NonNull TensorsData in) {
+        checkPipelineHandle();
+
+        if (in == null) {
+            throw new IllegalArgumentException("Input tensor data is null");
+        }
+
+        TensorsData out = nativeInvoke(mHandle, in);
+        if (out == null) {
+            throw new IllegalStateException("Failed to invoke the model");
+        }
+
+        return out;
+    }
+
+    /**
+     * Gets the information (tensor dimension, type, name and so on) of required input data for the given model.
+     *
+     * @return The tensors information
+     *
+     * @throws IllegalStateException if failed to get the input information
+     */
+    public TensorsInfo getInputInfo() {
+        checkPipelineHandle();
+
+        TensorsInfo info = nativeGetInputInfo(mHandle);
+        if (info == null) {
+            throw new IllegalStateException("Failed to get the input information");
+        }
+
+        return info;
+    }
+
+    /**
+     * Gets the information (tensor dimension, type, name and so on) of output data for the given model.
+     *
+     * @return The tensors information
+     *
+     * @throws IllegalStateException if failed to get the output information
+     */
+    public TensorsInfo getOutputInfo() {
+        checkPipelineHandle();
+
+        TensorsInfo info = nativeGetOutputInfo(mHandle);
+        if (info == null) {
+            throw new IllegalStateException("Failed to get the output information");
+        }
+
+        return info;
+    }
+
+    /**
+     * Internal method to check native handle.
+     *
+     * @throws IllegalStateException if the pipeline is not constructed
+     */
+    private void checkPipelineHandle() {
+        if (mHandle == 0) {
+            throw new IllegalStateException("The pipeline is not constructed");
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    @Override
+    public void close() {
+        if (mHandle > 0) {
+            nativeClose(mHandle);
+            mHandle = 0;
+        }
+    }
+}
diff --git a/api/android/api/src/com/samsung/android/nnstreamer/TensorsData.java b/api/android/api/src/com/samsung/android/nnstreamer/TensorsData.java
new file mode 100644 (file)
index 0000000..58ffeca
--- /dev/null
@@ -0,0 +1,125 @@
+package com.samsung.android.nnstreamer;
+
+import android.support.annotation.NonNull;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+
+/**
+ * Provides interfaces to handle tensor data frame.
+ */
+public final class TensorsData implements AutoCloseable {
+    private ArrayList<ByteBuffer> mDataList = new ArrayList<>();
+
+    /**
+     * Gets the number of tensors in tensors data.
+     *
+     * @return The number of tensors
+     */
+    public int getTensorsCount() {
+        return mDataList.size();
+    }
+
+    /**
+     * Adds a new tensor data.
+     *
+     * @param data The data object to be added
+     *
+     * @throws IllegalArgumentException if the data is not a byte buffer
+     * @throws IndexOutOfBoundsException when the maximum number of tensors in the list
+     */
+    public void addTensorData(@NonNull Object data) {
+        if (data == null || !(data instanceof ByteBuffer)) {
+            throw new IllegalArgumentException("Given data is not a byte buffer");
+        }
+
+        addTensorData((ByteBuffer) data);
+    }
+
+    /**
+     * Adds a new tensor data.
+     *
+     * @param data The tensor data to be added
+     *
+     * @throws IndexOutOfBoundsException when the maximum number of tensors in the list
+     */
+    public void addTensorData(@NonNull ByteBuffer data) {
+        int index = getTensorsCount();
+
+        if (index >= NNStreamer.TENSOR_SIZE_LIMIT) {
+            throw new IndexOutOfBoundsException("Max size of the tensors is " + NNStreamer.TENSOR_SIZE_LIMIT);
+        }
+
+        mDataList.add(convertBuffer(data));
+    }
+
+    /**
+     * Gets a tensor data of given index.
+     *
+     * @param index The index of the tensor in the list
+     *
+     * @return The tensor data
+     *
+     * @throws IndexOutOfBoundsException if the given index is invalid
+     */
+    public ByteBuffer getTensorData(int index) {
+        checkIndexBounds(index);
+        return mDataList.get(index);
+    }
+
+    /**
+     * Sets a tensor data.
+     *
+     * @param index The index of the tensor in the list
+     * @param data  The tensor data
+     *
+     * @throws IndexOutOfBoundsException if the given index is invalid
+     */
+    public void setTensorData(int index, @NonNull ByteBuffer data) {
+        checkIndexBounds(index);
+        mDataList.set(index, convertBuffer(data));
+    }
+
+    /**
+     * Internal method to check the index.
+     *
+     * @throws IndexOutOfBoundsException if the given index is invalid
+     */
+    private void checkIndexBounds(int index) {
+        if (index < 0 || index >= getTensorsCount()) {
+            throw new IndexOutOfBoundsException("Invalid index [" + index + "] of the tensors");
+        }
+    }
+
+    /**
+     * Internal method to convert the given data to direct buffer.
+     *
+     * @param data The tensor data
+     *
+     * @return The converted buffer
+     *
+     * @throws IllegalArgumentException if given data is null
+     */
+    private ByteBuffer convertBuffer(ByteBuffer data) {
+        if (data == null) {
+            throw new IllegalArgumentException("Given data is null");
+        }
+
+        if (data.isDirect() && data.order() == ByteOrder.nativeOrder()) {
+            return data;
+        }
+
+        ByteBuffer allocated = ByteBuffer.allocateDirect(data.capacity());
+
+        allocated.order(ByteOrder.nativeOrder());
+        allocated.put(data);
+
+        return allocated;
+    }
+
+    @Override
+    public void close() {
+        mDataList.clear();
+    }
+}
diff --git a/api/android/api/src/com/samsung/android/nnstreamer/TensorsInfo.java b/api/android/api/src/com/samsung/android/nnstreamer/TensorsInfo.java
new file mode 100644 (file)
index 0000000..cb42eb2
--- /dev/null
@@ -0,0 +1,231 @@
+package com.samsung.android.nnstreamer;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.ArrayList;
+
+/**
+ * Provides interfaces to handle tensors information.<br>
+ * <br>
+ * The data type of tensor in NNStreamer:<br>
+ * {@link NNStreamer#TENSOR_TYPE_INT32}<br>
+ * {@link NNStreamer#TENSOR_TYPE_UINT32}<br>
+ * {@link NNStreamer#TENSOR_TYPE_INT16}<br>
+ * {@link NNStreamer#TENSOR_TYPE_UINT16}<br>
+ * {@link NNStreamer#TENSOR_TYPE_INT8}<br>
+ * {@link NNStreamer#TENSOR_TYPE_UINT8}<br>
+ * {@link NNStreamer#TENSOR_TYPE_FLOAT64}<br>
+ * {@link NNStreamer#TENSOR_TYPE_FLOAT32}<br>
+ *
+ * @see NNStreamer#TENSOR_RANK_LIMIT
+ * @see NNStreamer#TENSOR_SIZE_LIMIT
+ */
+public final class TensorsInfo implements AutoCloseable {
+    private ArrayList<TensorInfo> mInfoList = new ArrayList<>();
+
+    /**
+     * Gets the number of tensors in tensors information.
+     *
+     * @return The number of tensors
+     */
+    public int getTensorsCount() {
+        return mInfoList.size();
+    }
+
+    /**
+     * Adds a new tensor information.
+     *
+     * @param type      The tensor data type
+     * @param dimension The tensor dimension
+     *
+     * @throws IndexOutOfBoundsException when the maximum number of tensors in the list
+     * @throws IllegalArgumentException if given param is null or invalid
+     */
+    public void addTensorInfo(int type, @NonNull int[] dimension) {
+        addTensorInfo(null, type, dimension);
+    }
+
+    /**
+     * Adds a new tensor information.
+     *
+     * @param name      The tensor name
+     * @param type      The tensor data type
+     * @param dimension The tensor dimension
+     *
+     * @throws IndexOutOfBoundsException when the maximum number of tensors in the list
+     * @throws IllegalArgumentException if given param is null or invalid
+     */
+    public void addTensorInfo(@Nullable String name, int type, @NonNull int[] dimension) {
+        int index = getTensorsCount();
+
+        if (index >= NNStreamer.TENSOR_SIZE_LIMIT) {
+            throw new IndexOutOfBoundsException("Max size of the tensors is " + NNStreamer.TENSOR_SIZE_LIMIT);
+        }
+
+        mInfoList.add(new TensorInfo(name, type, dimension));
+    }
+
+    /**
+     * Sets the tensor name.
+     *
+     * @param index The index of the tensor information in the list
+     * @param name  The tensor name
+     *
+     * @throws IndexOutOfBoundsException if the given index is invalid
+     */
+    public void setTensorName(int index, String name) {
+        checkIndexBounds(index);
+        mInfoList.get(index).setName(name);
+    }
+
+    /**
+     * Gets a tensor name of given index.
+     *
+     * @param index The index of the tensor information in the list
+     *
+     * @return The tensor name
+     *
+     * @throws IndexOutOfBoundsException if the given index is invalid
+     */
+    public String getTesorName(int index) {
+        checkIndexBounds(index);
+        return mInfoList.get(index).getName();
+    }
+
+    /**
+     * Sets the tensor data type.
+     *
+     * @param index The index of the tensor information in the list
+     * @param type  The tensor type
+     *
+     * @throws IndexOutOfBoundsException if the given index is invalid
+     * @throws IllegalArgumentException if the given type is unknown or unsupported type
+     */
+    public void setTensorType(int index, int type) {
+        checkIndexBounds(index);
+        mInfoList.get(index).setType(type);
+    }
+
+    /**
+     * Gets the tensor data type of given index.
+     *
+     * @param index The index of the tensor information in the list
+     *
+     * @return The tensor data type
+     *
+     * @throws IndexOutOfBoundsException if the given index is invalid
+     */
+    public int getTesorType(int index) {
+        checkIndexBounds(index);
+        return mInfoList.get(index).getType();
+    }
+
+    /**
+     * Sets the tensor dimension
+     *
+     * @param index     The index of the tensor information in the list
+     * @param dimension The tensor dimension
+     *
+     * @throws IndexOutOfBoundsException if the given index is invalid
+     * @throws IllegalArgumentException if the given dimension is null or invalid
+     */
+    public void setTensorDimension(int index, @NonNull int[] dimension) {
+        checkIndexBounds(index);
+        mInfoList.get(index).setDimension(dimension);
+    }
+
+    /**
+     * Gets the tensor dimension of given index.
+     *
+     * @param index The index of the tensor information in the list
+     *
+     * @return The tensor dimension
+     *
+     * @throws IndexOutOfBoundsException if the given index is invalid
+     */
+    public int[] getTesorDimension(int index) {
+        checkIndexBounds(index);
+        return mInfoList.get(index).getDimension();
+    }
+
+    /**
+     * Internal method to check the index.
+     *
+     * @throws IndexOutOfBoundsException if the given index is invalid
+     */
+    private void checkIndexBounds(int index) {
+        if (index < 0 || index >= getTensorsCount()) {
+            throw new IndexOutOfBoundsException("Invalid index [" + index + "] of the tensors");
+        }
+    }
+
+    @Override
+    public void close() {
+        mInfoList.clear();
+    }
+
+    /**
+     * Internal class for tensor information.
+     */
+    private static class TensorInfo {
+        private String name = null;
+        private int type = NNStreamer.TENSOR_TYPE_UNKNOWN;
+        private int[] dimension = new int[NNStreamer.TENSOR_RANK_LIMIT];
+
+        public TensorInfo(@Nullable String name, int type, @NonNull int[] dimension) {
+            setName(name);
+            setType(type);
+            setDimension(dimension);
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return this.name;
+        }
+
+        public void setType(int type) {
+            if (type < 0 || type >= NNStreamer.TENSOR_TYPE_UNKNOWN) {
+                throw new IllegalArgumentException("Given tensor type is unknown or unsupported type");
+            }
+
+            this.type = type;
+        }
+
+        public int getType() {
+            return this.type;
+        }
+
+        public void setDimension(@NonNull int[] dimension) {
+            if (dimension == null) {
+                throw new IllegalArgumentException("Given tensor dimension is null");
+            }
+
+            int length = dimension.length;
+
+            if (length > NNStreamer.TENSOR_RANK_LIMIT) {
+                throw new IllegalArgumentException("Max size of the tensor rank is " + NNStreamer.TENSOR_RANK_LIMIT);
+            }
+
+            for (int i = 0; i < length; i++) {
+                if (dimension[i] <= 0) {
+                    throw new IllegalArgumentException("The dimension should be a positive value");
+                }
+            }
+
+            System.arraycopy(dimension, 0, this.dimension, 0, length);
+
+            /* set 1 as default */
+            for (int i = length; i < NNStreamer.TENSOR_RANK_LIMIT; i++) {
+                this.dimension[i] = 1;
+            }
+        }
+
+        public int[] getDimension() {
+            return this.dimension;
+        }
+    }
+}
diff --git a/api/android/build.gradle b/api/android/build.gradle
new file mode 100644 (file)
index 0000000..a1526b9
--- /dev/null
@@ -0,0 +1,25 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        jcenter()
+        google()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.3.1'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        jcenter()
+        google()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/api/android/settings.gradle b/api/android/settings.gradle
new file mode 100644 (file)
index 0000000..5f53697
--- /dev/null
@@ -0,0 +1 @@
+include ':api'