[Android/Sample] add sample code to use library
authorJaeyun <jy1210.jung@samsung.com>
Fri, 19 Jul 2019 09:22:26 +0000 (18:22 +0900)
committerMyungJoo Ham <myungjoo.ham@samsung.com>
Thu, 25 Jul 2019 06:31:24 +0000 (15:31 +0900)
Add sample code to use nnstreamer library.
This sample includes basic example to run the single-shot and pipeline examples repeatedly.

Signed-off-by: Jaeyun Jung <jy1210.jung@samsung.com>
api/android/sample/build.gradle [new file with mode: 0644]
api/android/sample/libs/Readme.txt [new file with mode: 0644]
api/android/sample/proguard-rules.pro [new file with mode: 0644]
api/android/sample/src/main/AndroidManifest.xml [new file with mode: 0644]
api/android/sample/src/main/java/com/samsung/android/nnstreamer/sample/MainActivity.java [new file with mode: 0644]
api/android/sample/src/main/res/drawable/nnsuite_logo.png [new file with mode: 0644]
api/android/sample/src/main/res/layout/activity_main.xml [new file with mode: 0644]
api/android/sample/src/main/res/values/colors.xml [new file with mode: 0644]
api/android/sample/src/main/res/values/strings.xml [new file with mode: 0644]
api/android/sample/src/main/res/values/styles.xml [new file with mode: 0644]

diff --git a/api/android/sample/build.gradle b/api/android/sample/build.gradle
new file mode 100644 (file)
index 0000000..3d4fcb5
--- /dev/null
@@ -0,0 +1,25 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 28
+    defaultConfig {
+        applicationId "com.samsung.android.nnstreamer.sample"
+        minSdkVersion 24
+        targetSdkVersion 28
+        versionCode 1
+        versionName "1.0"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    buildToolsVersion '28.0.3'
+}
+
+dependencies {
+    implementation fileTree(include: ['*.aar'], dir: 'libs')
+    implementation 'com.android.support:appcompat-v7:28.0.0'
+    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+}
diff --git a/api/android/sample/libs/Readme.txt b/api/android/sample/libs/Readme.txt
new file mode 100644 (file)
index 0000000..8ac7b1e
--- /dev/null
@@ -0,0 +1 @@
+To run this sample, copy nnstreamer-api library file into 'libs' directory.\r
diff --git a/api/android/sample/proguard-rules.pro b/api/android/sample/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/sample/src/main/AndroidManifest.xml b/api/android/sample/src/main/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..e3f3789
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.samsung.android.nnstreamer.sample">
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/nnsuite_logo"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity android:name=".MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/api/android/sample/src/main/java/com/samsung/android/nnstreamer/sample/MainActivity.java b/api/android/sample/src/main/java/com/samsung/android/nnstreamer/sample/MainActivity.java
new file mode 100644 (file)
index 0000000..fc6a40c
--- /dev/null
@@ -0,0 +1,502 @@
+package com.samsung.android.nnstreamer.sample;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.os.Environment;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
+
+import com.samsung.android.nnstreamer.NNStreamer;
+import com.samsung.android.nnstreamer.Pipeline;
+import com.samsung.android.nnstreamer.SingleShot;
+import com.samsung.android.nnstreamer.TensorsData;
+import com.samsung.android.nnstreamer.TensorsInfo;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+
+/**
+ * Sample code to run the application with nnstreamer-api.
+ * Before building this sample, copy nnstreamer-api library file into 'libs' directory.
+ */
+public class MainActivity extends Activity {
+    private static final String TAG = "NNStreamer-Sample";
+
+    private static final int PERMISSION_REQUEST_CODE = 3;
+    private static final String[] requiredPermissions = new String[] {
+            Manifest.permission.READ_EXTERNAL_STORAGE
+    };
+
+    private boolean initialized = false;
+    private boolean isFailed = false;
+    private CountDownTimer exampleTimer = null;
+    private int exampleRun = 0;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_main);
+
+        /* check permissions */
+        for (String permission : requiredPermissions) {
+            if (!checkPermission(permission)) {
+                ActivityCompat.requestPermissions(this,
+                        requiredPermissions, PERMISSION_REQUEST_CODE);
+                return;
+            }
+        }
+
+        initNNStreamer();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        if (initialized) {
+            /* set timer to run examples */
+            exampleRun = 0;
+            isFailed = false;
+            setExampleTimer(200);
+        }
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+
+        stopExampleTimer();
+    }
+
+    /**
+     * Check the permission is granted.
+     */
+    private boolean checkPermission(final String permission) {
+        return (ContextCompat.checkSelfPermission(this, permission)
+                == PackageManager.PERMISSION_GRANTED);
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
+        if (requestCode == PERMISSION_REQUEST_CODE) {
+            for (int grant : grantResults) {
+                if (grant != PackageManager.PERMISSION_GRANTED) {
+                    Log.i(TAG, "Permission denied, close app.");
+                    finish();
+                    return;
+                }
+            }
+
+            initNNStreamer();
+            return;
+        }
+
+        finish();
+    }
+
+    /**
+     * Initialize NNStreamer.
+     */
+    private void initNNStreamer() {
+        if (initialized) {
+            return;
+        }
+
+        try {
+            initialized = NNStreamer.initialize(this);
+        } catch(Exception e) {
+            e.printStackTrace();
+            Log.e(TAG, e.getMessage());
+        } finally {
+            if (initialized) {
+                Log.i(TAG, "Version: " + NNStreamer.getVersion());
+            } else {
+                Log.e(TAG, "Failed to initialize NNStreamer");
+                finish();
+            }
+        }
+    }
+
+    /**
+     * Set timer to run the examples.
+     */
+    private void setExampleTimer(long time) {
+        stopExampleTimer();
+
+        exampleTimer = new CountDownTimer(time, time) {
+            @Override
+            public void onTick(long millisUntilFinished) {
+            }
+
+            @Override
+            public void onFinish() {
+                /* run the examples repeatedly */
+                if (exampleRun > 15) {
+                    Log.d(TAG, "Stop timer to run example");
+
+                    if (isFailed) {
+                        Log.d(TAG, "Error occurs while running the examples");
+                    }
+
+                    return;
+                }
+
+                int option = (exampleRun % 5);
+
+                if (option == 1) {
+                    Log.d(TAG, "==== Run pipeline example with state callback ====");
+                    runPipe(true);
+                } else if (option == 2) {
+                    Log.d(TAG, "==== Run pipeline example ====");
+                    runPipe(false);
+                } else if (option == 3) {
+                    Log.d(TAG, "==== Run pipeline example with valve ====");
+                    runPipeValve();
+                } else if (option == 4) {
+                    Log.d(TAG, "==== Run pipeline example with switch ====");
+                    runPipeSwitch();
+                } else {
+                    Log.d(TAG, "==== Run single-shot example ====");
+                    runSingle();
+                }
+
+                exampleRun++;
+                setExampleTimer(500);
+            }
+        };
+
+        exampleTimer.start();
+    }
+
+    /**
+     * Cancel example timer.
+     */
+    private void stopExampleTimer() {
+        if (exampleTimer != null) {
+            exampleTimer.cancel();
+            exampleTimer = null;
+        }
+    }
+
+    /**
+     * Print tensors info.
+     *
+     * The data type of tensor in NNStreamer:
+     * {@link NNStreamer#TENSOR_TYPE_INT32}
+     * {@link NNStreamer#TENSOR_TYPE_UINT32}
+     * {@link NNStreamer#TENSOR_TYPE_INT16}
+     * {@link NNStreamer#TENSOR_TYPE_UINT16}
+     * {@link NNStreamer#TENSOR_TYPE_INT8}
+     * {@link NNStreamer#TENSOR_TYPE_UINT8}
+     * {@link NNStreamer#TENSOR_TYPE_FLOAT64}
+     * {@link NNStreamer#TENSOR_TYPE_FLOAT32}
+     * {@link NNStreamer#TENSOR_TYPE_UNKNOWN}
+     *
+     * The maximum rank that NNStreamer supports.
+     * {@link NNStreamer#TENSOR_RANK_LIMIT}
+     *
+     * The maximum number of tensor instances that tensors may have.
+     * {@link NNStreamer#TENSOR_SIZE_LIMIT}
+     */
+    private void printTensorsInfo(TensorsInfo info) {
+        int num = info.getTensorsCount();
+
+        Log.d(TAG, "The number of tensors in info: " + num);
+        for (int i = 0; i < num; i++) {
+            int[] dim = info.getTesorDimension(i);
+
+            Log.d(TAG, "Info index " + i +
+                    " name: " + info.getTesorName(0) +
+                    " type: " + info.getTesorType(0) +
+                    " dim: " + dim[0] + ":" + dim[1] + ":" + dim[2] + ":" + dim[3]);
+        }
+    }
+
+    /**
+     * Print tensors data.
+     *
+     * The maximum number of tensor instances that tensors may have.
+     * {@link NNStreamer#TENSOR_SIZE_LIMIT}
+     */
+    private void printTensorsData(TensorsData data) {
+        int num = data.getTensorsCount();
+
+        Log.d(TAG, "The number of tensors in data: " + num);
+        for (int i = 0; i < num; i++) {
+            ByteBuffer buffer = data.getTensorData(i);
+
+            Log.d(TAG, "Data index " + i + " received " + buffer.capacity());
+        }
+    }
+
+    /**
+     * Example to run single-shot.
+     */
+    private void runSingle() {
+        /* example with image classification tf-lite model */
+        String root = Environment.getExternalStorageDirectory().getAbsolutePath();
+        File model = new File(root + "/nnstreamer/tflite_model_img/mobilenet_v1_1.0_224_quant.tflite");
+
+        if (!model.exists()) {
+            Log.w(TAG, "Cannot find the model file");
+            return;
+        }
+
+        try {
+            SingleShot single = new SingleShot(model);
+
+            /* single-shot invoke */
+            for (int i = 0; i < 15; i++) {
+                /* dummy input */
+                TensorsData in = new TensorsData();
+                in.addTensorData(ByteBuffer.allocateDirect(3 * 224 * 224));
+
+                Log.d(TAG, "Try to invoke data " + (i + 1));
+
+                TensorsData out = single.invoke(in);
+                printTensorsData(out);
+
+                Thread.sleep(50);
+            }
+
+            Log.d(TAG, "Get input tensors info");
+            printTensorsInfo(single.getInputInfo());
+
+            Log.d(TAG, "Get output tensors info");
+            printTensorsInfo(single.getOutputInfo());
+
+            single.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+            Log.e(TAG, e.getMessage());
+            isFailed = true;
+        }
+    }
+
+    /**
+     * Example to run pipeline.
+     *
+     * The state of pipeline:
+     * {@link NNStreamer#PIPELINE_STATE_UNKNOWN}
+     * {@link NNStreamer#PIPELINE_STATE_NULL}
+     * {@link NNStreamer#PIPELINE_STATE_READY}
+     * {@link NNStreamer#PIPELINE_STATE_PAUSED}
+     * {@link NNStreamer#PIPELINE_STATE_PLAYING}
+     */
+    private void runPipe(boolean addStateCb) {
+        /* example with image classification tf-lite model */
+        String root = Environment.getExternalStorageDirectory().getAbsolutePath();
+        File model = new File(root + "/nnstreamer/tflite_model_img/mobilenet_v1_1.0_224_quant.tflite");
+
+        if (!model.exists()) {
+            Log.w(TAG, "Cannot find the model file");
+            return;
+        }
+
+        try {
+            String desc = "appsrc name=srcx ! other/tensor,dimension=(string)3:224:224:1,type=(string)uint8,framerate=(fraction)0/1 ! " +
+                    "tensor_filter framework=tensorflow-lite model=" + model.getAbsolutePath() + " ! " +
+                    "tensor_sink name=sinkx";
+
+            /* pipeline state callback */
+            Pipeline.StateChangeCallback stateCb = null;
+
+            if (addStateCb) {
+                stateCb = new Pipeline.StateChangeCallback() {
+                    @Override
+                    public void onStateChanged(int state) {
+                        Log.d(TAG, "The pipeline state changed to " + state);
+                    }
+                };
+            }
+
+            Pipeline pipe = new Pipeline(desc, stateCb);
+
+            /* register sink callback */
+            pipe.setSinkCallback("sinkx", new Pipeline.NewDataCallback() {
+                int received = 0;
+
+                @Override
+                public void onNewDataReceived(TensorsData data, TensorsInfo info) {
+                    Log.d(TAG, "Received new data callback " + (++received));
+
+                    printTensorsInfo(info);
+                    printTensorsData(data);
+                }
+            });
+
+            Log.d(TAG, "Current state is " + pipe.getState());
+
+            /* start pipeline */
+            pipe.start();
+
+            /* push input buffer */
+            for (int i = 0; i < 15; i++) {
+                /* dummy input */
+                TensorsData in = new TensorsData();
+                in.addTensorData(ByteBuffer.allocateDirect(3 * 224 * 224));
+
+                Log.d(TAG, "Push input data " + (i + 1));
+
+                pipe.inputData("srcx", in);
+                Thread.sleep(50);
+            }
+
+            Log.d(TAG, "Current state is " + pipe.getState());
+
+            pipe.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+            Log.e(TAG, e.getMessage());
+            isFailed = true;
+        }
+    }
+
+    /**
+     * Example to run pipeline with valve.
+     */
+    private void runPipeValve() {
+        try {
+            String desc = "appsrc name=srcx ! other/tensor,dimension=(string)3:100:100:1,type=(string)uint8,framerate=(fraction)0/1 ! " +
+                    "tee name=t " +
+                    "t. ! queue ! tensor_sink name=sink1 " +
+                    "t. ! queue ! valve name=valvex ! tensor_sink name=sink2";
+
+            Pipeline pipe = new Pipeline(desc);
+
+            /* register sink callback */
+            pipe.setSinkCallback("sink1", new Pipeline.NewDataCallback() {
+                int received = 0;
+
+                @Override
+                public void onNewDataReceived(TensorsData data, TensorsInfo info) {
+                    Log.d(TAG, "Received new data callback at sink1 " + (++received));
+
+                    printTensorsInfo(info);
+                    printTensorsData(data);
+                }
+            });
+
+            pipe.setSinkCallback("sink2", new Pipeline.NewDataCallback() {
+                int received = 0;
+
+                @Override
+                public void onNewDataReceived(TensorsData data, TensorsInfo info) {
+                    Log.d(TAG, "Received new data callback at sink2 " + (++received));
+
+                    printTensorsInfo(info);
+                    printTensorsData(data);
+                }
+            });
+
+            /* start pipeline */
+            pipe.start();
+
+            /* push input buffer */
+            for (int i = 0; i < 15; i++) {
+                /* dummy input */
+                TensorsData in = new TensorsData();
+                in.addTensorData(ByteBuffer.allocateDirect(3 * 100 * 100));
+
+                Log.d(TAG, "Push input data " + (i + 1));
+
+                pipe.inputData("srcx", in);
+                Thread.sleep(50);
+
+                if (i == 10) {
+                    /* close valve */
+                    pipe.controlValve("valvex", false);
+                }
+            }
+
+            pipe.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+            Log.e(TAG, e.getMessage());
+            isFailed = true;
+        }
+    }
+
+    /**
+     * Example to run pipeline with output-selector.
+     */
+    private void runPipeSwitch() {
+        try {
+            /* Note that the sink element needs option 'async=false'
+             *
+             * Prerolling problem
+             * For running the pipeline, set async=false in the sink element when using an output selector.
+             * The pipeline state can be changed to paused after all sink element receive buffer.
+             */
+            String desc = "appsrc name=srcx ! other/tensor,dimension=(string)3:100:100:1,type=(string)uint8,framerate=(fraction)0/1 ! " +
+                    "output-selector name=outs " +
+                    "outs.src_0 ! tensor_sink name=sink1 async=false " +
+                    "outs.src_1 ! tensor_sink name=sink2 async=false";
+
+            Pipeline pipe = new Pipeline(desc);
+
+            /* register sink callback */
+            pipe.setSinkCallback("sink1", new Pipeline.NewDataCallback() {
+                int received = 0;
+
+                @Override
+                public void onNewDataReceived(TensorsData data, TensorsInfo info) {
+                    Log.d(TAG, "Received new data callback at sink1 " + (++received));
+
+                    printTensorsInfo(info);
+                    printTensorsData(data);
+                }
+            });
+
+            pipe.setSinkCallback("sink2", new Pipeline.NewDataCallback() {
+                int received = 0;
+
+                @Override
+                public void onNewDataReceived(TensorsData data, TensorsInfo info) {
+                    Log.d(TAG, "Received new data callback at sink2 " + (++received));
+
+                    printTensorsInfo(info);
+                    printTensorsData(data);
+                }
+            });
+
+            /* start pipeline */
+            pipe.start();
+
+            /* push input buffer */
+            for (int i = 0; i < 15; i++) {
+                /* dummy input */
+                TensorsData in = new TensorsData();
+                in.addTensorData(ByteBuffer.allocateDirect(3 * 100 * 100));
+
+                Log.d(TAG, "Push input data " + (i + 1));
+
+                pipe.inputData("srcx", in);
+                Thread.sleep(50);
+
+                if (i == 10) {
+                    /* select pad */
+                    pipe.selectSwitchPad("outs", "src_1");
+                }
+            }
+
+            /* get pad list of output-selector */
+            String[] pads = pipe.getSwitchPads("outs");
+            Log.d(TAG, "Total pad in output-selector: " + pads.length);
+            for (String pad : pads) {
+                Log.d(TAG, "Pad name: " + pad);
+            }
+
+            pipe.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+            Log.e(TAG, e.getMessage());
+            isFailed = true;
+        }
+    }
+}
diff --git a/api/android/sample/src/main/res/drawable/nnsuite_logo.png b/api/android/sample/src/main/res/drawable/nnsuite_logo.png
new file mode 100644 (file)
index 0000000..723f278
Binary files /dev/null and b/api/android/sample/src/main/res/drawable/nnsuite_logo.png differ
diff --git a/api/android/sample/src/main/res/layout/activity_main.xml b/api/android/sample/src/main/res/layout/activity_main.xml
new file mode 100644 (file)
index 0000000..6682bf8
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="NNStreamer API Example"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</android.support.constraint.ConstraintLayout>
diff --git a/api/android/sample/src/main/res/values/colors.xml b/api/android/sample/src/main/res/values/colors.xml
new file mode 100644 (file)
index 0000000..69b2233
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#008577</color>
+    <color name="colorPrimaryDark">#00574B</color>
+    <color name="colorAccent">#D81B60</color>
+</resources>
diff --git a/api/android/sample/src/main/res/values/strings.xml b/api/android/sample/src/main/res/values/strings.xml
new file mode 100644 (file)
index 0000000..eddf1b7
--- /dev/null
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">NNStreamer API Sample</string>
+</resources>
diff --git a/api/android/sample/src/main/res/values/styles.xml b/api/android/sample/src/main/res/values/styles.xml
new file mode 100644 (file)
index 0000000..5885930
--- /dev/null
@@ -0,0 +1,11 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+</resources>