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 +Android Alexa Demo -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"): + +Android Alexa Demo + + +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;