diff --git a/README.md b/README.md
index 133b8ee6..05391255 100644
--- a/README.md
+++ b/README.md
@@ -293,28 +293,10 @@ Thanks to @patrickjquinn and @grimlockrocks, we now have examples of using Snowb
## Compile an Android Wrapper
- cd swig/Android
- # Make sure you set up the NDKROOT variable in Makefile before you run.
- # We have only tested with NDK version r11c.
- make
-
-(Warning: please do this on \*nix platforms. Snowboy does not support Windows yet)
-
-Using Snowboy library on Android devices is a little bit tricky. We have only tested with NDK version r11c. We do not support r12 yet because of the removal of armeabi-v7a-hard ABI in r12. We have compiled Snowboy using Android's cross-compilation toolchain for ARMV7 architecture, see the library here `lib/android/armv7a/libsnowboy-detect.a`. We then use SWIG to generate the Java wrapper, and use Android's cross-compilation toolchain to generate the corresponding JNI libraries. After running `make`, two directories will be created: `java` and `jniLibs`. Copy these two directories to your Android app directory (e.g., `app/src/main/`) and you should be able to call Snowboy funcitons within Java.
-
-To initialize Snowboy detector in Java:
+Full README and tutorial is in [Android README](examples/Android/README.md) and here's a screenshot:
- # Assume you put the model related files under /sdcard/snowboy/
- SnowboyDetect snowboyDetector = new SnowboyDetect("/sdcard/snowboy/common.res",
- "/sdcard/snowboy/snowboy.umdl");
- snowboyDetector.SetSensitivity("0.45"); // Sensitivity for each hotword
- snowboyDetector.SetAudioGain(2.0); // Audio gain for detection
+
-To run hotword detection in Java:
-
- int result = snowboyDetector.RunDetection(buffer, buffer.length); // buffer is a short array.
-
-You may want to play with the frequency of the calls to `RunDetection()`, which controls the CPU usage and the detection latency.
## Quick Start for Python Demo
diff --git a/examples/Android/README.md b/examples/Android/README.md
new file mode 100644
index 00000000..a4c429e9
--- /dev/null
+++ b/examples/Android/README.md
@@ -0,0 +1,74 @@
+# Snowboy Demo on Adroid
+
+Note:
+
+1. supported building platforms are Android Studio running on Mac OS X or Ubuntu. Windows is not supported.
+2. supported target CPU is ARMv7 (most Android phones run on ARM CPUs)
+
+## General Workflow
+
+1. Install `swig`. For Mac, do `brew install swig`; for Ubuntu, do `sudo apt-get install swig3.0`. Make sure your `swig` version is at least `3.0.10`.
+
+2. Go to `swig/Android` and build swig wrappers for Snowboy:
+
+ cd swig/Android
+ make
+
+ Ths will generate a cross-compiled library for ARM:
+
+ jniLibs/armeabi-v7a/libsnowboy-detect-android.so
+
+ and a few Java wrapper files:
+
+ java
+ └── ai
+ └── kitt
+ └── snowboy
+ ├── SnowboyDetect.java
+ ├── snowboy.java
+ └── snowboyJNI.java
+
+ The generated `.so` and `.java` files are hyperlinked to the `examples/Android/SnowboyAlexaDemo` folder.
+
+3. Use Android Studio to open the project in `examples/Android/SnowboyAlexaDemo` and run it.
+
+Screenshot (say "Alexa" after clicking "Start"):
+
+
+
+
+Don't forget to disable the "debug" option when releasing your Android App!
+
+Note: If you need to copy the Android demo to another folder, please use the `-RL` option of `cp` to replace the relative symbol links with real files:
+
+ cp -RL SnowboyAlexaDemo Other_Folder
+
+Note: The sample app will save/overwrite all audio to a file (`recording.pcm`). Make sure you do not leave it on for a long time.
+
+## Useful Code Snippets
+
+
+To initialize Snowboy detector in Java:
+
+ # Assume you put the model related files under /sdcard/snowboy/
+ SnowboyDetect snowboyDetector = new SnowboyDetect("/sdcard/snowboy/common.res",
+ "/sdcard/snowboy/snowboy.umdl");
+ snowboyDetector.SetSensitivity("0.45"); // Sensitivity for each hotword
+ snowboyDetector.SetAudioGain(2.0); // Audio gain for detection
+
+To run hotword detection in Java:
+
+ int result = snowboyDetector.RunDetection(buffer, buffer.length); // buffer is a short array.
+
+You may want to play with the frequency of the calls to `RunDetection()`, which controls the CPU usage and the detection latency.
+
+
+## TODO
+
+The following TODOs will be fixed by 2017/4 in the upcoming Snowboy v1.2.0 release.
+
+- [x] softfloating point support with OpenBlas
+- [x] upgrade NDK version to newer than r11c
+- [x] NDK toolchain building: remove `--stl=libc++` option
+
+
diff --git a/examples/Android/SnowboyAlexaDemo/.classpath b/examples/Android/SnowboyAlexaDemo/.classpath
new file mode 100644
index 00000000..b76ec6cd
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/.classpath
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/Android/SnowboyAlexaDemo/.gitignore b/examples/Android/SnowboyAlexaDemo/.gitignore
new file mode 100644
index 00000000..dff166a0
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/.gitignore
@@ -0,0 +1,14 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+*.apk
+*.ap_
+.metadata/
+.idea/workspace.xml
+.idea/tasks.xml
diff --git a/examples/Android/SnowboyAlexaDemo/.idea/.name b/examples/Android/SnowboyAlexaDemo/.idea/.name
new file mode 100644
index 00000000..ef643d46
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/.idea/.name
@@ -0,0 +1 @@
+SnowboyAlexaDemo
\ No newline at end of file
diff --git a/examples/Android/SnowboyAlexaDemo/.idea/compiler.xml b/examples/Android/SnowboyAlexaDemo/.idea/compiler.xml
new file mode 100644
index 00000000..96cc43ef
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/.idea/compiler.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/Android/SnowboyAlexaDemo/.idea/copyright/profiles_settings.xml b/examples/Android/SnowboyAlexaDemo/.idea/copyright/profiles_settings.xml
new file mode 100644
index 00000000..e7bedf33
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/examples/Android/SnowboyAlexaDemo/.idea/encodings.xml b/examples/Android/SnowboyAlexaDemo/.idea/encodings.xml
new file mode 100644
index 00000000..240b96f2
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/.idea/encodings.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/Android/SnowboyAlexaDemo/.idea/gradle.xml b/examples/Android/SnowboyAlexaDemo/.idea/gradle.xml
new file mode 100644
index 00000000..47bd81ff
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/.idea/gradle.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/Android/SnowboyAlexaDemo/.idea/misc.xml b/examples/Android/SnowboyAlexaDemo/.idea/misc.xml
new file mode 100644
index 00000000..5d199810
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/.idea/misc.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/Android/SnowboyAlexaDemo/.idea/modules.xml b/examples/Android/SnowboyAlexaDemo/.idea/modules.xml
new file mode 100644
index 00000000..fd9bf431
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/Android/SnowboyAlexaDemo/.idea/runConfigurations.xml b/examples/Android/SnowboyAlexaDemo/.idea/runConfigurations.xml
new file mode 100644
index 00000000..7f68460d
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/Android/SnowboyAlexaDemo/.project b/examples/Android/SnowboyAlexaDemo/.project
new file mode 100644
index 00000000..0d6e9608
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/.project
@@ -0,0 +1,33 @@
+
+
+ Alexa19
+
+
+
+
+
+ com.android.ide.eclipse.adt.ResourceManagerBuilder
+
+
+
+
+ com.android.ide.eclipse.adt.PreCompilerBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ com.android.ide.eclipse.adt.ApkBuilder
+
+
+
+
+
+ com.android.ide.eclipse.adt.AndroidNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/examples/Android/SnowboyAlexaDemo/AndroidManifest.xml b/examples/Android/SnowboyAlexaDemo/AndroidManifest.xml
new file mode 100644
index 00000000..11fb5c10
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/AndroidManifest.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/Android/SnowboyAlexaDemo/assets/snowboy/alexa_02092017.umdl b/examples/Android/SnowboyAlexaDemo/assets/snowboy/alexa_02092017.umdl
new file mode 120000
index 00000000..b2d1387e
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/assets/snowboy/alexa_02092017.umdl
@@ -0,0 +1 @@
+../../../../../resources/alexa_02092017.umdl
\ No newline at end of file
diff --git a/examples/Android/SnowboyAlexaDemo/assets/snowboy/common.res b/examples/Android/SnowboyAlexaDemo/assets/snowboy/common.res
new file mode 120000
index 00000000..bb7bed58
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/assets/snowboy/common.res
@@ -0,0 +1 @@
+../../../../../resources/common.res
\ No newline at end of file
diff --git a/examples/Android/SnowboyAlexaDemo/assets/snowboy/ding.wav b/examples/Android/SnowboyAlexaDemo/assets/snowboy/ding.wav
new file mode 120000
index 00000000..0b0c1a18
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/assets/snowboy/ding.wav
@@ -0,0 +1 @@
+../../../../../resources/ding.wav
\ No newline at end of file
diff --git a/examples/Android/SnowboyAlexaDemo/build.gradle b/examples/Android/SnowboyAlexaDemo/build.gradle
new file mode 100644
index 00000000..0d824897
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/build.gradle
@@ -0,0 +1,52 @@
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.3.0'
+ }
+}
+apply plugin: 'android'
+
+dependencies {
+ compile fileTree(include: '*.jar', dir: 'libs')
+}
+
+android {
+ signingConfigs {
+ }
+ compileSdkVersion 25
+ buildToolsVersion '25.0.0'
+ compileOptions.encoding = 'ISO-8859-1'
+ sourceSets {
+ main {
+ manifest.srcFile 'AndroidManifest.xml'
+ java.srcDirs = ['src']
+ resources.srcDirs = ['src']
+ aidl.srcDirs = ['src']
+ renderscript.srcDirs = ['src']
+ res.srcDirs = ['res']
+ assets.srcDirs = ['assets']
+ }
+
+ // Move the tests to tests/java, tests/res, etc...
+ instrumentTest.setRoot('tests')
+
+ // Move the build types to build-types/
+ // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
+ // This moves them out of them default location under src//... which would
+ // conflict with src/ being used by the main source set.
+ // Adding new build types or product flavors should be accompanied
+ // by a similar customization.
+ debug.setRoot('build-types/debug')
+ release.setRoot('build-types/release')
+ }
+ buildTypes {
+ release {
+ }
+ }
+ defaultConfig {
+ }
+ productFlavors {
+ }
+}
diff --git a/examples/Android/SnowboyAlexaDemo/gradle/wrapper/gradle-wrapper.jar b/examples/Android/SnowboyAlexaDemo/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..8c0fb64a
Binary files /dev/null and b/examples/Android/SnowboyAlexaDemo/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/examples/Android/SnowboyAlexaDemo/gradle/wrapper/gradle-wrapper.properties b/examples/Android/SnowboyAlexaDemo/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..dff2a985
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Mar 07 14:27:10 PST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
diff --git a/examples/Android/SnowboyAlexaDemo/gradlew b/examples/Android/SnowboyAlexaDemo/gradlew
new file mode 100755
index 00000000..91a7e269
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/examples/Android/SnowboyAlexaDemo/gradlew.bat b/examples/Android/SnowboyAlexaDemo/gradlew.bat
new file mode 100644
index 00000000..aec99730
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/examples/Android/SnowboyAlexaDemo/ic_launcher-web.png b/examples/Android/SnowboyAlexaDemo/ic_launcher-web.png
new file mode 100644
index 00000000..8e8513d3
Binary files /dev/null and b/examples/Android/SnowboyAlexaDemo/ic_launcher-web.png differ
diff --git a/examples/Android/SnowboyAlexaDemo/project.properties b/examples/Android/SnowboyAlexaDemo/project.properties
new file mode 100644
index 00000000..22c1e80d
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/project.properties
@@ -0,0 +1,17 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Indicates whether an apk should be generated for each density.
+split.density=false
+# Project target.
+target=android-17
+apk-configurations=
diff --git a/examples/Android/SnowboyAlexaDemo/res/drawable/icon.png b/examples/Android/SnowboyAlexaDemo/res/drawable/icon.png
new file mode 100644
index 00000000..75024841
Binary files /dev/null and b/examples/Android/SnowboyAlexaDemo/res/drawable/icon.png differ
diff --git a/examples/Android/SnowboyAlexaDemo/res/layout/main.xml b/examples/Android/SnowboyAlexaDemo/res/layout/main.xml
new file mode 100644
index 00000000..3ad80bb9
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/res/layout/main.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/Android/SnowboyAlexaDemo/res/mipmap-hdpi/ic_launcher.png b/examples/Android/SnowboyAlexaDemo/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..6c762252
Binary files /dev/null and b/examples/Android/SnowboyAlexaDemo/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/examples/Android/SnowboyAlexaDemo/res/mipmap-mdpi/ic_launcher.png b/examples/Android/SnowboyAlexaDemo/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..b589d0a4
Binary files /dev/null and b/examples/Android/SnowboyAlexaDemo/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/examples/Android/SnowboyAlexaDemo/res/mipmap-xhdpi/ic_launcher.png b/examples/Android/SnowboyAlexaDemo/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..fe7e6845
Binary files /dev/null and b/examples/Android/SnowboyAlexaDemo/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/examples/Android/SnowboyAlexaDemo/res/mipmap-xxhdpi/ic_launcher.png b/examples/Android/SnowboyAlexaDemo/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..7b04ebd3
Binary files /dev/null and b/examples/Android/SnowboyAlexaDemo/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/examples/Android/SnowboyAlexaDemo/res/mipmap-xxxhdpi/ic_launcher.png b/examples/Android/SnowboyAlexaDemo/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..06cbec7a
Binary files /dev/null and b/examples/Android/SnowboyAlexaDemo/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/examples/Android/SnowboyAlexaDemo/res/values/strings.xml b/examples/Android/SnowboyAlexaDemo/res/values/strings.xml
new file mode 100644
index 00000000..ee2e186f
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/res/values/strings.xml
@@ -0,0 +1,8 @@
+
+
+ SnowboyAlexaDemo
+ Start
+ Stop
+ Play
+ Stop
+
diff --git a/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/AppResCopy.java b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/AppResCopy.java
new file mode 100644
index 00000000..3ecbedee
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/AppResCopy.java
@@ -0,0 +1,64 @@
+package ai.kitt.snowboy;
+
+import android.content.Context;
+import android.util.Log;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+
+public class AppResCopy {
+ private final static String TAG = AppResCopy.class.getSimpleName();
+ private static String envWorkSpace = Constants.DEFAULT_WORK_SPACE;
+
+ private static void copyFilesFromAssets(Context context, String assetsSrcDir, String sdcardDstDir, boolean override) {
+ try {
+ String fileNames[] = context.getAssets().list(assetsSrcDir);
+ if (fileNames.length > 0) {
+ Log.i(TAG, assetsSrcDir +" directory has "+fileNames.length+" files.\n");
+ File dir = new File(sdcardDstDir);
+ if (!dir.exists()) {
+ if (!dir.mkdirs()) {
+ Log.e(TAG, "mkdir failed: "+sdcardDstDir);
+ return;
+ } else {
+ Log.i(TAG, "mkdir ok: "+sdcardDstDir);
+ }
+ } else {
+ Log.w(TAG, sdcardDstDir+" already exists! ");
+ }
+ for (String fileName : fileNames) {
+ copyFilesFromAssets(context,assetsSrcDir + "/" + fileName,sdcardDstDir+"/"+fileName, override);
+ }
+ } else {
+ Log.i(TAG, assetsSrcDir +" is file\n");
+ File outFile = new File(sdcardDstDir);
+ if (outFile.exists()) {
+ if (override) {
+ outFile.delete();
+ Log.e(TAG, "overriding file "+ sdcardDstDir +"\n");
+ } else {
+ Log.e(TAG, "file "+ sdcardDstDir +" already exists. No override.\n");
+ return;
+ }
+ }
+ InputStream is = context.getAssets().open(assetsSrcDir);
+ FileOutputStream fos = new FileOutputStream(outFile);
+ byte[] buffer = new byte[1024];
+ int byteCount=0;
+ while ((byteCount=is.read(buffer)) != -1) {
+ fos.write(buffer, 0, byteCount);
+ }
+ fos.flush();
+ is.close();
+ fos.close();
+ Log.i(TAG, "copy to "+sdcardDstDir+" ok!");
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void copyResFromAssetsToSD(Context context) {
+ copyFilesFromAssets(context, Constants.ASSETS_RES_DIR, envWorkSpace+"/", true);
+ }
+}
diff --git a/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/Constants.java b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/Constants.java
new file mode 100644
index 00000000..7e758044
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/Constants.java
@@ -0,0 +1,12 @@
+package ai.kitt.snowboy;
+import java.io.File;
+import android.os.Environment;
+
+public class Constants {
+ public static final String ASSETS_RES_DIR = "snowboy";
+ public static final String DEFAULT_WORK_SPACE = Environment.getExternalStorageDirectory().getAbsolutePath() + "/snowboy/";
+ public static final String ACTIVE_UMDL = "alexa_02092017.umdl";
+ public static final String ACTIVE_RES = "common.res";
+ public static final String SAVE_AUDIO = Constants.DEFAULT_WORK_SPACE + File.separatorChar + "recording.pcm";
+ public static final int SAMPLE_RATE = 16000;
+}
diff --git a/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/Demo.java b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/Demo.java
new file mode 100644
index 00000000..fd584576
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/Demo.java
@@ -0,0 +1,252 @@
+package ai.kitt.snowboy;
+
+import ai.kitt.snowboy.audio.RecordingThread;
+import ai.kitt.snowboy.audio.PlaybackThread;
+
+import android.app.Activity;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.text.Html;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.content.Context;
+
+import ai.kitt.snowboy.audio.AudioDataSaver;
+import ai.kitt.snowboy.demo.R;
+
+
+public class Demo extends Activity {
+
+ private Button record_button;
+ private Button play_button;
+ private TextView log;
+ private ScrollView logView;
+ static String strLog = null;
+
+ private int preVolume = -1;
+ private static long activeTimes = 0;
+
+ private RecordingThread recordingThread;
+ private PlaybackThread playbackThread;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.main);
+ setUI();
+
+ setProperVolume();
+
+ AppResCopy.copyResFromAssetsToSD(this);
+
+ activeTimes = 0;
+ recordingThread = new RecordingThread(handle, new AudioDataSaver());
+ playbackThread = new PlaybackThread();
+ }
+
+ void showToast(CharSequence msg) {
+ Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
+ }
+
+ private void setUI() {
+ record_button = (Button) findViewById(R.id.btn_test1);
+ record_button.setOnClickListener(record_button_handle);
+ record_button.setEnabled(true);
+
+ play_button = (Button) findViewById(R.id.btn_test2);
+ play_button.setOnClickListener(play_button_handle);
+ play_button.setEnabled(true);
+
+ log = (TextView)findViewById(R.id.log);
+ logView = (ScrollView)findViewById(R.id.logView);
+ }
+
+ private void setMaxVolume() {
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ preVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ updateLog(" ----> preVolume = "+preVolume, "green");
+ int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ updateLog(" ----> maxVolume = "+maxVolume, "green");
+ audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, maxVolume, 0);
+ int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ updateLog(" ----> currentVolume = "+currentVolume, "green");
+ }
+
+ private void setProperVolume() {
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ preVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ updateLog(" ----> preVolume = "+preVolume, "green");
+ int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ updateLog(" ----> maxVolume = "+maxVolume, "green");
+ int properVolume = (int) ((float) maxVolume * 0.2);
+ audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, properVolume, 0);
+ int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ updateLog(" ----> currentVolume = "+currentVolume, "green");
+ }
+
+ private void restoreVolume() {
+ if(preVolume>=0) {
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, preVolume, 0);
+ updateLog(" ----> set preVolume = "+preVolume, "green");
+ int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ updateLog(" ----> currentVolume = "+currentVolume, "green");
+ }
+ }
+
+ private void startRecording() {
+ recordingThread.startRecording();
+ updateLog(" ----> recording started ...", "green");
+ record_button.setText(R.string.btn1_stop);
+ }
+
+ private void stopRecording() {
+ recordingThread.stopRecording();
+ updateLog(" ----> recording stopped ", "green");
+ record_button.setText(R.string.btn1_start);
+ }
+
+ private void startPlayback() {
+ updateLog(" ----> playback started ...", "green");
+ play_button.setText(R.string.btn2_stop);
+ // (new PcmPlayer()).playPCM();
+ playbackThread.startPlayback();
+ }
+
+ private void stopPlayback() {
+ updateLog(" ----> playback stopped ", "green");
+ play_button.setText(R.string.btn2_start);
+ playbackThread.stopPlayback();
+ }
+
+ private void sleep() {
+ try { Thread.sleep(500);
+ } catch (Exception e) {}
+ }
+
+ private OnClickListener record_button_handle = new OnClickListener() {
+ // @Override
+ public void onClick(View arg0) {
+ if(record_button.getText().equals(getResources().getString(R.string.btn1_start))) {
+ stopPlayback();
+ sleep();
+ startRecording();
+ } else {
+ stopRecording();
+ sleep();
+ }
+ }
+ };
+
+ private OnClickListener play_button_handle = new OnClickListener() {
+ // @Override
+ public void onClick(View arg0) {
+ if (play_button.getText().equals(getResources().getString(R.string.btn2_start))) {
+ stopRecording();
+ sleep();
+ startPlayback();
+ } else {
+ stopPlayback();
+ }
+ }
+ };
+
+ public Handler handle = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ MsgEnum message = MsgEnum.getMsgEnum(msg.what);
+ switch(message) {
+ case MSG_ACTIVE:
+ activeTimes++;
+ updateLog(" ----> Detected " + activeTimes + " times", "green");
+ // Toast.makeText(Demo.this, "Active "+activeTimes, Toast.LENGTH_SHORT).show();
+ showToast("Active "+activeTimes);
+ break;
+ case MSG_INFO:
+ updateLog(" ----> "+message);
+ break;
+ case MSG_VAD_SPEECH:
+ updateLog(" ----> normal voice", "blue");
+ break;
+ case MSG_VAD_NOSPEECH:
+ updateLog(" ----> no speech", "blue");
+ break;
+ case MSG_ERROR:
+ updateLog(" ----> " + msg.toString(), "red");
+ break;
+ default:
+ super.handleMessage(msg);
+ break;
+ }
+ }
+ };
+
+ public void updateLog(final String text) {
+
+ log.post(new Runnable() {
+ @Override
+ public void run() {
+ if (currLogLineNum >= MAX_LOG_LINE_NUM) {
+ int st = strLog.indexOf(" ");
+ strLog = strLog.substring(st+4);
+ } else {
+ currLogLineNum++;
+ }
+ String str = ""+text+""+" ";
+ strLog = (strLog == null || strLog.length() == 0) ? str : strLog + str;
+ log.setText(Html.fromHtml(strLog));
+ }
+ });
+ logView.post(new Runnable() {
+ @Override
+ public void run() {
+ logView.fullScroll(ScrollView.FOCUS_DOWN);
+ }
+ });
+ }
+
+ static int MAX_LOG_LINE_NUM = 200;
+ static int currLogLineNum = 0;
+
+ public void updateLog(final String text, final String color) {
+ log.post(new Runnable() {
+ @Override
+ public void run() {
+ if(currLogLineNum>=MAX_LOG_LINE_NUM) {
+ int st = strLog.indexOf(" ");
+ strLog = strLog.substring(st+4);
+ } else {
+ currLogLineNum++;
+ }
+ String str = ""+text+""+" ";
+ strLog = (strLog == null || strLog.length() == 0) ? str : strLog + str;
+ log.setText(Html.fromHtml(strLog));
+ }
+ });
+ logView.post(new Runnable() {
+ @Override
+ public void run() {
+ logView.fullScroll(ScrollView.FOCUS_DOWN);
+ }
+ });
+ }
+
+ private void emptyLog() {
+ strLog = null;
+ log.setText("");
+ }
+
+ @Override
+ public void onDestroy() {
+ restoreVolume();
+ recordingThread.stopRecording();
+ super.onDestroy();
+ }
+}
diff --git a/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/MsgEnum.java b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/MsgEnum.java
new file mode 100644
index 00000000..9aef0216
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/MsgEnum.java
@@ -0,0 +1,18 @@
+package ai.kitt.snowboy;
+
+public enum MsgEnum {
+ MSG_VAD_END,
+ MSG_VAD_NOSPEECH,
+ MSG_VAD_SPEECH,
+ MSG_VOLUME_NOTIFY,
+ MSG_WAV_DATAINFO,
+ MSG_RECORD_START,
+ MSG_RECORD_STOP,
+ MSG_ACTIVE,
+ MSG_ERROR,
+ MSG_INFO;
+
+ public static MsgEnum getMsgEnum(int i) {
+ return MsgEnum.values()[i];
+ }
+}
diff --git a/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/SnowboyDetect.java b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/SnowboyDetect.java
new file mode 120000
index 00000000..1a09ecb6
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/SnowboyDetect.java
@@ -0,0 +1 @@
+../../../../../../../swig/Android/java/ai/kitt/snowboy/SnowboyDetect.java
\ No newline at end of file
diff --git a/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/audio/AudioDataReceivedListener.java b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/audio/AudioDataReceivedListener.java
new file mode 100644
index 00000000..e51fdffc
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/audio/AudioDataReceivedListener.java
@@ -0,0 +1,7 @@
+package ai.kitt.snowboy.audio;
+
+public interface AudioDataReceivedListener {
+ void start();
+ void onAudioDataReceived(byte[] data, int length);
+ void stop();
+}
diff --git a/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/audio/AudioDataSaver.java b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/audio/AudioDataSaver.java
new file mode 100644
index 00000000..e847847f
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/audio/AudioDataSaver.java
@@ -0,0 +1,70 @@
+package ai.kitt.snowboy.audio;
+
+import java.io.BufferedOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import android.util.Log;
+
+import ai.kitt.snowboy.Constants;
+
+public class AudioDataSaver implements AudioDataReceivedListener {
+
+ private static final String TAG = AudioDataSaver.class.getSimpleName();
+
+ private File saveFile = null;
+ private DataOutputStream dataOutputStreamInstance = null;
+
+ public AudioDataSaver() {
+ saveFile = new File(Constants.SAVE_AUDIO);
+ Log.e(TAG, Constants.SAVE_AUDIO);
+ }
+
+ @Override
+ public void start() {
+ if(null != saveFile) {
+ if (saveFile.exists()) {
+ saveFile.delete();
+ }
+ try {
+ saveFile.createNewFile();
+ } catch (IOException e) {
+ Log.e(TAG, "IO Exception on creating audio file " + saveFile.toString(), e);
+ }
+
+ try {
+ BufferedOutputStream bufferedStreamInstance = new BufferedOutputStream(
+ new FileOutputStream(this.saveFile));
+ dataOutputStreamInstance = new DataOutputStream(bufferedStreamInstance);
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException("Cannot Open File", e);
+ }
+ }
+ }
+
+ @Override
+ public void onAudioDataReceived(byte[] data, int length) {
+ try {
+ if(null != dataOutputStreamInstance) {
+ dataOutputStreamInstance.write(data, 0, length);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "IO Exception on saving audio file " + saveFile.toString(), e);
+ }
+ }
+
+ @Override
+ public void stop() {
+ if(null != dataOutputStreamInstance) {
+ try {
+ dataOutputStreamInstance.close();
+ } catch (IOException e) {
+ Log.e(TAG, "IO Exception on finishing saving audio file " + saveFile.toString(), e);
+ }
+ Log.e(TAG, "Recording saved to " + saveFile.toString());
+ }
+ }
+}
diff --git a/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/audio/PlaybackThread.java b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/audio/PlaybackThread.java
new file mode 100644
index 00000000..43f033e5
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/audio/PlaybackThread.java
@@ -0,0 +1,109 @@
+package ai.kitt.snowboy.audio;
+
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.util.Log;
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.ShortBuffer;
+
+import ai.kitt.snowboy.Constants;
+
+public class PlaybackThread {
+ private static final String TAG = PlaybackThread.class.getSimpleName();
+
+ public PlaybackThread() {
+ }
+
+ private Thread thread;
+ private boolean shouldContinue;
+
+ public boolean playing() {
+ return thread != null;
+ }
+
+ public void startPlayback() {
+ if (thread != null)
+ return;
+
+ // Start streaming in a thread
+ shouldContinue = true;
+ thread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ play();
+ }
+ });
+ thread.start();
+ }
+
+ public void stopPlayback() {
+ if (thread == null)
+ return;
+
+ shouldContinue = false;
+ thread = null;
+ }
+
+ public short[] readPCM() {
+ try {
+ File recordFile = new File(Constants.SAVE_AUDIO);
+ InputStream inputStream = new FileInputStream(recordFile);
+ BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
+ DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);
+
+ byte[] audioData = new byte[(int)recordFile.length()];
+
+ // int i = 0;
+ // while (dataInputStream.available() > 0) {
+ // audioData[i] = dataInputStream.readByte();
+ // i++;
+ // }
+ dataInputStream.read(audioData);
+ dataInputStream.close();
+ Log.v(TAG, "audioData size: " + audioData.length);
+
+ ShortBuffer sb = ByteBuffer.wrap(audioData).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
+ short[] samples = new short[sb.limit() - sb.position()];
+ sb.get(samples);
+ return samples;
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Cannot find saved audio file", e);
+ } catch (IOException e) {
+ Log.e(TAG, "IO Exception on saved audio file", e);
+ }
+ return null;
+ }
+
+ private void play() {
+ short[] samples = this.readPCM();
+ int shortSizeInBytes = Short.SIZE / Byte.SIZE;
+ int bufferSizeInBytes = samples.length * shortSizeInBytes;
+ Log.v(TAG, "shortSizeInBytes: " + shortSizeInBytes + " bufferSizeInBytes: " + bufferSizeInBytes);
+
+ AudioTrack audioTrack = new AudioTrack(
+ AudioManager.STREAM_MUSIC,
+ Constants.SAMPLE_RATE,
+ AudioFormat.CHANNEL_OUT_MONO,
+ AudioFormat.ENCODING_PCM_16BIT,
+ bufferSizeInBytes,
+ AudioTrack.MODE_STREAM);
+
+ audioTrack.play();
+
+ audioTrack.write(samples, 0, samples.length);
+ Log.v(TAG, "Audio playback started");
+
+ if (!shouldContinue) {
+ audioTrack.release();
+ }
+ }
+}
diff --git a/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/audio/RecordingThread.java b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/audio/RecordingThread.java
new file mode 100644
index 00000000..fa768c27
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/audio/RecordingThread.java
@@ -0,0 +1,150 @@
+package ai.kitt.snowboy.audio;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import ai.kitt.snowboy.Constants;
+import ai.kitt.snowboy.MsgEnum;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
+import android.media.MediaPlayer;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import ai.kitt.snowboy.SnowboyDetect;
+
+public class RecordingThread {
+ static { System.loadLibrary("snowboy-detect-android"); }
+
+ private static final String TAG = RecordingThread.class.getSimpleName();
+
+ private static final String ACTIVE_RES = Constants.ACTIVE_RES;
+ private static final String ACTIVE_UMDL = Constants.ACTIVE_UMDL;
+
+ private boolean shouldContinue;
+ private AudioDataReceivedListener listener = null;
+ private Handler handler = null;
+ private Thread thread;
+
+ private static String strEnvWorkSpace = Constants.DEFAULT_WORK_SPACE;
+ private String activeModel = strEnvWorkSpace+ACTIVE_UMDL;
+ private String commonRes = strEnvWorkSpace+ACTIVE_RES;
+
+ private SnowboyDetect detector = new SnowboyDetect(commonRes, activeModel);
+ private MediaPlayer player = new MediaPlayer();
+
+ public RecordingThread(Handler handler, AudioDataReceivedListener listener) {
+ this.handler = handler;
+ this.listener = listener;
+ }
+
+ private void sendMessage(MsgEnum what, Object obj){
+ if (null != handler) {
+ Message msg = handler.obtainMessage(what.ordinal(), obj);
+ handler.sendMessage(msg);
+ }
+ }
+
+ public void startRecording() {
+ detector.SetSensitivity("0.6");
+ //-detector.SetAudioGain(1);
+ detector.ApplyFrontend(true);
+ try {
+ player.setDataSource(strEnvWorkSpace+"ding.wav");
+ player.prepare();
+ } catch (IOException e) {
+ Log.e(TAG, "Playing ding sound error", e);
+ }
+ if (thread != null)
+ return;
+
+ shouldContinue = true;
+ thread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ record();
+ }
+ });
+ thread.start();
+ }
+
+ public void stopRecording() {
+ if (thread == null)
+ return;
+
+ shouldContinue = false;
+ thread = null;
+ }
+
+ private void record() {
+ Log.v(TAG, "Start");
+ android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
+
+ // Buffer size in bytes: for 0.1 second of audio
+ int bufferSize = (int)(Constants.SAMPLE_RATE * 0.1 * 2);
+ if (bufferSize == AudioRecord.ERROR || bufferSize == AudioRecord.ERROR_BAD_VALUE) {
+ bufferSize = Constants.SAMPLE_RATE * 2;
+ }
+
+ byte[] audioBuffer = new byte[bufferSize];
+ AudioRecord record = new AudioRecord(
+ MediaRecorder.AudioSource.DEFAULT,
+ Constants.SAMPLE_RATE,
+ AudioFormat.CHANNEL_IN_MONO,
+ AudioFormat.ENCODING_PCM_16BIT,
+ bufferSize);
+
+ if (record.getState() != AudioRecord.STATE_INITIALIZED) {
+ Log.e(TAG, "Audio Record can't initialize!");
+ return;
+ }
+ record.startRecording();
+ if (null != listener) {
+ listener.start();
+ }
+ Log.v(TAG, "Start recording");
+
+ long shortsRead = 0;
+ while (shouldContinue) {
+ record.read(audioBuffer, 0, audioBuffer.length, AudioRecord.READ_BLOCKING);
+
+ if (null != listener) {
+ listener.onAudioDataReceived(audioBuffer, audioBuffer.length);
+ }
+
+ // Converts to short array.
+ short[] audioData = new short[audioBuffer.length / 2];
+ ByteBuffer.wrap(audioBuffer).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(audioData);
+
+ shortsRead += audioData.length;
+
+ // Snowboy hotword detection.
+ int result = detector.RunDetection(audioData, audioData.length);
+
+ if (result == -2) {
+ // post a higher CPU usage:
+ sendMessage(MsgEnum.MSG_VAD_NOSPEECH, null);
+ } else if (result == -1) {
+ sendMessage(MsgEnum.MSG_ERROR, "Unknown Detection Error");
+ } else if (result == 0) {
+ // post a higher CPU usage:
+ sendMessage(MsgEnum.MSG_VAD_SPEECH, null);
+ } else if (result > 0) {
+ sendMessage(MsgEnum.MSG_ACTIVE, null);
+ Log.i("Snowboy: ", "Hotword " + Integer.toString(result) + " detected!");
+ player.start();
+ }
+ }
+
+ record.stop();
+ record.release();
+
+ if (null != listener) {
+ listener.stop();
+ }
+ Log.v(TAG, String.format("Recording stopped. Samples read: %d", shortsRead));
+ }
+}
diff --git a/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/snowboyJNI.java b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/snowboyJNI.java
new file mode 120000
index 00000000..b8ff5625
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/src/ai/kitt/snowboy/snowboyJNI.java
@@ -0,0 +1 @@
+../../../../../../../swig/Android/java/ai/kitt/snowboy/snowboyJNI.java
\ No newline at end of file
diff --git a/examples/Android/SnowboyAlexaDemo/src/main/jniLibs b/examples/Android/SnowboyAlexaDemo/src/main/jniLibs
new file mode 120000
index 00000000..76f885f7
--- /dev/null
+++ b/examples/Android/SnowboyAlexaDemo/src/main/jniLibs
@@ -0,0 +1 @@
+../../../../../swig/Android/jniLibs
\ No newline at end of file
diff --git a/examples/C++/patches/portaudio.patch b/examples/C++/patches/portaudio.patch
deleted file mode 100644
index 2cc73dde..00000000
--- a/examples/C++/patches/portaudio.patch
+++ /dev/null
@@ -1,41 +0,0 @@
---- Makefile.in 2016-01-09 14:05:04.096356637 -0500
-+++ Makefile_new.in 2016-01-09 14:04:56.667925681 -0500
-@@ -193,6 +193,8 @@
- for include in $(INCLUDES); do \
- $(INSTALL_DATA) -m 644 $(top_srcdir)/include/$$include $(DESTDIR)$(includedir)/$$include; \
- done
-+ $(INSTALL_DATA) -m 644 $(top_srcdir)/src/common/pa_ringbuffer.h $(DESTDIR)$(includedir)/$$include
-+ $(INSTALL_DATA) -m 644 $(top_srcdir)/src/common/pa_util.h $(DESTDIR)$(includedir)/$$include
- $(INSTALL) -d $(DESTDIR)$(libdir)/pkgconfig
- $(INSTALL) -m 644 portaudio-2.0.pc $(DESTDIR)$(libdir)/pkgconfig/portaudio-2.0.pc
- @echo ""
---- configure 2016-03-08 18:00:08.000000000 -0800
-+++ configure_new 2016-03-08 17:59:21.000000000 -0800
-@@ -15787,7 +15787,7 @@
- $as_echo "#define PA_USE_COREAUDIO 1" >>confdefs.h
-
-
-- CFLAGS="$CFLAGS -I\$(top_srcdir)/src/os/unix -Werror"
-+ CFLAGS="$CFLAGS -I\$(top_srcdir)/src/os/unix -Wall"
- LIBS="-framework CoreAudio -framework AudioToolbox -framework AudioUnit -framework Carbon"
-
- if test "x$enable_mac_universal" = "xyes" ; then
-@@ -15819,8 +15819,17 @@
- elif xcodebuild -version -sdk macosx10.9 Path >/dev/null 2>&1 ; then
- mac_version_min="-mmacosx-version-min=10.4"
- mac_sysroot="-isysroot `xcodebuild -version -sdk macosx10.9 Path`"
-+ elif xcodebuild -version -sdk macosx10.10 Path >/dev/null 2>&1 ; then
-+ mac_version_min="-mmacosx-version-min=10.4"
-+ mac_sysroot="-isysroot `xcodebuild -version -sdk macosx10.10 Path`"
-+ elif xcodebuild -version -sdk macosx10.11 Path >/dev/null 2>&1 ; then
-+ mac_version_min="-mmacosx-version-min=10.4"
-+ mac_sysroot="-isysroot `xcodebuild -version -sdk macosx10.11 Path`"
-+ elif xcodebuild -version -sdk macosx10.12 Path >/dev/null 2>&1 ; then
-+ mac_version_min="-mmacosx-version-min=10.4"
-+ mac_sysroot="-isysroot `xcodebuild -version -sdk macosx10.12 Path`"
- else
-- as_fn_error $? "Couldn't find 10.5, 10.6, 10.7, 10.8 or 10.9 SDK" "$LINENO" 5
-+ as_fn_error $? "Couldn't find 10.5, 10.6, 10.7, 10.8, 10.9, 10.10, 10.11 or 10.12 SDK" "$LINENO" 5
- fi
- esac
-
diff --git a/lib/android/armv7a/libsnowboy-detect.a b/lib/android/armv7a/libsnowboy-detect.a
index 69fff246..f3a5afba 100644
Binary files a/lib/android/armv7a/libsnowboy-detect.a and b/lib/android/armv7a/libsnowboy-detect.a differ
diff --git a/swig/Android/Makefile b/swig/Android/Makefile
index c5812398..48d8c432 100644
--- a/swig/Android/Makefile
+++ b/swig/Android/Makefile
@@ -1,16 +1,16 @@
# Example Makefile that wrappers snowboy c++ library (snowboy-detect.a) through
# JNI interface, using swig.
-# When you extract Android toolchain from Android NDK, make sure you supply
-# --stl=libc++ option. This Makefile is optimized for armv7-a architecture.
-# Also, please make sure "unzip" is installed.
+# This Makefile is optimized for armv7-a architecture. Also, please make sure
+# "unzip" is installed.
# Please use swig-3.0.10 or up.
SWIG := swig
# Please specify your NDK root directory here.
+NDK_VERSION="r14b"
NDKINSTALLEDROOT := $(PWD)/ndk_install
-NDKROOT := $(PWD)/android-ndk-r11c
+NDKROOT := $(PWD)/android-ndk-${NDK_VERSION}
SNOWBOYDETECTSWIGITF = snowboy-detect-swig.i
SNOWBOYDETECTSWIGOBJ = snowboy-detect-swig.o
@@ -34,17 +34,13 @@ ifeq ($(ARCH), arm)
STRIP := $(NDKINSTALLEDROOT)/bin/arm-linux-androideabi-strip
OPENBLASTARGET := ARMV7
SNOWBOYDETECTLIBFILE = $(TOPDIR)/lib/android/armv7a/libsnowboy-detect.a
- CXXFLAGS += -shared -std=c++0x -rdynamic -I$(TOPDIR) -Wl,--fix-cortex-a8 \
- -Wl,--no-warn-mismatch -Wl,--no-undefined -Wl,-z,noexecstack \
- -Wl,-z,relro -Wl,-z,now -Wl,--warn-shared-textrel -Wl,--fatal-warnings \
- -Wno-sign-compare -Wa,--noexecstack -Wformat -Werror=format-security \
- -fno-rtti -ffunction-sections -funwind-tables -fstack-protector-strong \
- -fomit-frame-pointer -fno-strict-aliasing -finline-limit=64 \
- -fno-strict-aliasing -mthumb -no-canonical-prefixes -march=armv7-a \
- -mfpu=vfpv3-d16 -mhard-float -D_NDK_MATH_NO_SOFTFP=1 -DANDROID
+ CXXFLAGS += -std=c++0x -rdynamic -I$(TOPDIR) -Werror -Wall \
+ -fsigned-char -fpic -fPIC -mfloat-abi=softfp -march=armv7-a -mfpu=neon \
+ -DNDEBUG -ffast-math -fomit-frame-pointer -O3 -pie -fPIE -DHAVE_NEON=1 \
+ -fno-strict-aliasing -Wno-unused-function -shared
LDLIBS += \
- -L$(NDKROOT)/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a-hard/thumb/ \
- -lc++_static -lgcc -lm_hard -ldl -lc -lm -llog
+ -L$(NDKROOT)/sources/cxx-stl/gnu-libstdc++/4.9/libs/armeabi-v7a \
+ -lgnustl_static -lsupc++ -lgcc -ldl -lc -lm -llog -pthread
SNOWBOYDETECTSWIGLIBFILE := jniLibs/armeabi-v7a/$(SNOWBOYDETECTSWIGLIBFILE)
SNOWBOYDETECTSWIGLIBNAME := $(shell basename $(SNOWBOYDETECTSWIGLIBFILE))
endif
@@ -55,7 +51,7 @@ all: $(SNOWBOYSWIGLIBFILE) $(SNOWBOYDETECTSWIGLIBFILE)
$(MAKE) -C ${@D} ${@F}
$(NDKINSTALLEDROOT):
- @-./install_ndk.sh
+ @-./install_ndk.sh ${NDK_VERSION}
$(OPENBLASLIBFILE): $(NDKINSTALLEDROOT)
@-./install_openblas.sh $(CC) $(AR) $(OPENBLASTARGET)
diff --git a/swig/Android/install_ndk.sh b/swig/Android/install_ndk.sh
index 391af1b9..8e928997 100755
--- a/swig/Android/install_ndk.sh
+++ b/swig/Android/install_ndk.sh
@@ -1,26 +1,27 @@
#!/bin/bash
-# This script installs NDK r11c
+# This script installs NDK with version number as parameter
+# Usage: ./install_ndk.sh [ndk version] (such as "r14b")
UNAME_INFO=`uname -a`
NDK_REPOSITORY_URL="https://dl.google.com/android/repository/"
+NDK_VERSION=$1
if [[ $UNAME_INFO == *"Darwin"* ]]; then
- if [ ! -f android-ndk-r11c-darwin-x86_64.zip ]; then
- wget -T 10 -t 3 ${NDK_REPOSITORY_URL}/android-ndk-r11c-darwin-x86_64.zip \
- -O android-ndk-r11c-darwin-x86_64.zip || exit 1;
+ if [ ! -d "android-ndk-${NDK_VERSION}" ]; then
+ wget -T 10 -t 3 ${NDK_REPOSITORY_URL}/android-ndk-${NDK_VERSION}-darwin-x86_64.zip \
+ -O android-ndk-${NDK_VERSION}-darwin-x86_64.zip || exit 1;
fi
- unzip android-ndk-r11c-darwin-x86_64.zip 1>/dev/null || exit 1;
+ unzip android-ndk-${NDK_VERSION}-darwin-x86_64.zip 1>/dev/null || exit 1;
elif [[ $UNAME_INFO == *"Linux"* ]]; then
- if [ ! -f android-ndk-r11c-linux-x86_64.zip ]; then
- wget -T 10 -t 3 ${NDK_REPOSITORY_URL}/android-ndk-r11c-linux-x86_64.zip \
- -O android-ndk-r11c-linux-x86_64.zip || exit 1;
+ if [ ! -d "android-ndk-${NDK_VERSION}" ]; then
+ wget -T 10 -t 3 ${NDK_REPOSITORY_URL}/android-ndk-${NDK_VERSION}-linux-x86_64.zip \
+ -O android-ndk-${NDK_VERSION}-linux-x86_64.zip || exit 1;
fi
- unzip android-ndk-r11c-linux-x86_64.zip 1>/dev/null || exit 1;
+ unzip android-ndk-${NDK_VERSION}-linux-x86_64.zip 1>/dev/null || exit 1;
else
echo "Your platform is not supported yet." || exit 1;
fi
-./android-ndk-r11c/build/tools/make-standalone-toolchain.sh \
- --arch=arm --platform=android-17 --install-dir=`pwd`/ndk_install \
- --use-llvm --stl=libc++ || exit 1;
+./android-ndk-${NDK_VERSION}/build/tools/make-standalone-toolchain.sh \
+ --arch=arm --platform=android-14 --install-dir=`pwd`/ndk_install || exit 1;
diff --git a/swig/Android/install_openblas.sh b/swig/Android/install_openblas.sh
index 4e1b4afe..57fe3c19 100755
--- a/swig/Android/install_openblas.sh
+++ b/swig/Android/install_openblas.sh
@@ -1,21 +1,22 @@
#!/bin/bash
-# This script compiles OpenBLAS for Android with the given architecture. The
-# prebuild Android NDK toolchains do not include Fortran, hence parts like
-# LAPACK will not be built.
+# This script compiles OpenBLAS for Android with ARM architecture. The prebuilt
+# Android NDK toolchains do not include Fortran, hence parts like LAPACK will
+# not be built.
CC=$1
AR=$2
TARGET=$3
-if [ ! -f OpenBLAS-0.2.18.tar.gz ]; then
- wget -T 10 -t 3 https://codeload.github.com/xianyi/OpenBLAS/tar.gz/v0.2.18 \
- -O OpenBLAS-0.2.18.tar.gz || exit 1;
+if [ ! -d "OpenBLAS-Android" ]; then
+ git clone https://github.com/xianyi/OpenBLAS.git OpenBLAS-Android
+ cd OpenBLAS-Android
+ git checkout arm_soft_fp_abi || exit 1;
+ git reset --hard b5c96fcfcdc82945502a2303116a64d89985daf5 || exit 1;
+ cd ..
fi
-tar -xvzf OpenBLAS-0.2.18.tar.gz 1>/dev/null || exit 1;
-mv OpenBLAS-0.2.18 OpenBLAS-Android
-
cd OpenBLAS-Android
-make TARGET=${TARGET} HOSTCC=gcc CC=${CC} AR=${AR} NOFORTRAN=1 || exit 1;
+make USE_THREAD=0 TARGET=${TARGET} HOSTCC=gcc CC=${CC} AR=${AR} \
+ NOFORTRAN=1 ARM_SOFTFP_ABI=1 libs || exit 1;
make PREFIX=`pwd`/install install || exit 1;