diff --git a/.github/workflows/android-appcenter.yml b/.github/workflows/android-appcenter.yml new file mode 100644 index 0000000..4ef6f94 --- /dev/null +++ b/.github/workflows/android-appcenter.yml @@ -0,0 +1,68 @@ +name: Android AppCenter Tests + +on: + workflow_dispatch: + push: + branches: [ main ] + paths: + - '.github/workflows/android-appcenter.yml' + - 'demo/android/Activity/**' + - '!demo/android/Activity/README.md' + - 'resources/audio_samples/**' + pull_request: + branches: [ main, 'v[0-9]+.[0-9]+'] + paths: + - '.github/workflows/android-appcenter.yml' + - 'demo/android/Activity/**' + - '!demo/android/Activity/README.md' + - 'resources/audio_samples/**' + +defaults: + run: + working-directory: demo/android/Activity + +jobs: + build: + name: Run Android Tests on AppCenter + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup Node.js environment + uses: actions/setup-node@v2.4.0 + + - name: Install AppCenter CLI + run: npm install -g appcenter-cli + + - name: set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + + - name: Binding pre-build + run: ./gradlew assemble + working-directory: binding/android + + - name: Copy test_resources + run: ./copy_test_resources.sh + + - name: Inject AccessKey + run: echo pvTestingAccessKey="${{secrets.PV_VALID_ACCESS_KEY}}" >> local.properties + + - name: Build app + run: ./gradlew assembleDebug + + - name: Build androidTest + run: ./gradlew assembleAndroidTest + + - name: Run tests on AppCenter + run: appcenter test run espresso + --token ${{secrets.APPCENTERAPITOKEN}} + --app "Picovoice/Koala-Android" + --devices "Picovoice/android-min-max" + --app-path koala-activity-demo-app/build/outputs/apk/debug/koala-activity-demo-app-debug.apk + --test-series "koala-android" + --locale "en_US" + --build-dir koala-activity-demo-app/build/outputs/apk/androidTest/debug diff --git a/.github/workflows/android-perf.yml b/.github/workflows/android-perf.yml new file mode 100644 index 0000000..3760b6a --- /dev/null +++ b/.github/workflows/android-perf.yml @@ -0,0 +1,83 @@ +name: Android Performance + +on: + workflow_dispatch: + push: + branches: [ main ] + paths: + - '.github/workflows/android-perf.yml' + - 'lib/android/**' + - 'lib/common/**' + - 'resources/audio_samples/**' + pull_request: + branches: [ main, 'v[0-9]+.[0-9]+'] + paths: + - '.github/workflows/android-perf.yml' + - 'lib/android/**' + - 'lib/common/**' + - 'resources/audio_samples/**' + +defaults: + run: + working-directory: demo/android/Activity + +jobs: + build: + name: Run Android Speed Tests on AppCenter + runs-on: ubuntu-latest + + strategy: + matrix: + device: [single-android, 32bit-android] + include: + - device: single-android + performanceThresholdSec: 0.4 + - device: 32bit-android + performanceThresholdSec: 3.0 + + steps: + - uses: actions/checkout@v2 + + - name: Setup Node.js environment + uses: actions/setup-node@v2.4.0 + + - name: Install AppCenter CLI + run: npm install -g appcenter-cli + + - name: set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + + - name: Copy test_resources + run: ./copy_test_resources.sh + + - name: Inject AccessKey + run: echo pvTestingAccessKey="${{secrets.PV_VALID_ACCESS_KEY}}" >> local.properties + + - name: Inject Number of Iterations + run: echo numTestIterations="30" >> local.properties + + - name: Inject Proc Performance Threshold + run: echo performanceThresholdSec="${{ matrix.performanceThresholdSec }}" >> local.properties + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build app + run: ./gradlew assembleDebug + + - name: Build androidTest + run: ./gradlew assembleAndroidTest + + - name: Run tests on AppCenter + run: appcenter test run espresso + --token ${{secrets.APPCENTERAPITOKEN}} + --app "Picovoice/Koala-Android" + --devices "Picovoice/${{ matrix.device }}" + --app-path koala-activity-demo-app/build/outputs/apk/debug/koala-activity-demo-app-debug.apk + --test-series "koala-android" + --locale "en_US" + --build-dir koala-activity-demo-app/build/outputs/apk/androidTest/debug + diff --git a/README.md b/README.md index 764ca9a..b8a69ee 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,13 @@ Koala is an on-device noise suppression engine. Koala is: - [AccessKey](#accesskey) - [Demos](#demos) - [Python](#python-demos) + - [Android](#android-demo) - [iOS](#ios-demo) - [C](#c-demos) - [Web](#web-demo) - [SDKs](#sdks) - [Python](#python) + - [Android](#android) - [iOS](#ios) - [C](#c) - [Web](#web) @@ -65,6 +67,13 @@ koala_demo_file \ Replace `${ACCESS_KEY}` with yours obtained from Picovoice Console. +### Android Demo + +Using [Android Studio](https://developer.android.com/studio/index.html), open +[demo/android/Activity](./demo/android/Activity) as an Android project and then run the application. + +Replace `"${YOUR_ACCESS_KEY_HERE}"` in the file [MainActivity.java](./demo/android/Activity/koala-activity-demo-app/src/main/java/ai/picovoice/koalaactivitydemo/MainActivity.java) with your `AccessKey`. + ### iOS Demo Copy your `AccessKey` into the `ACCESS_KEY` variable inside [`ViewModel.swift`](./demo/ios/KoalaDemo/KoalaDemo/ViewModel.swift). @@ -153,6 +162,42 @@ while True: Finally, when done be sure to explicitly release the resources using `koala.delete()`. +### Android + +To include the package in your Android project, ensure you have included `mavenCentral()` in your top-level `build.gradle` file and then add the following to your app's `build.gradle`: + +```groovy +dependencies { + implementation 'ai.picovoice:koala-android:${LATEST_VERSION}' +} +``` + +Create an instance of the engine and enhance audio in real-time: + +```java +import ai.picovoice.koala.*; + +final String accessKey = "${ACCESS_KEY}"; // AccessKey obtained from Picovoice Console (https://console.picovoice.ai/) + +short[] getNextAudioFrame() { + // .. get audioFrame + return audioFrame; +} + +try { + Koala koala = new Koala.Builder() + .setAccessKey(accessKey) + .build(appContext); + + while true { + short[] enhancedFrame = koala.process(getNextAudioFrame()); + }; + +} catch (KoalaException ex) { } +``` + +Replace `${ACCESS_KEY}` with yours obtained from [Picovoice Console](https://console.picovoice.ai/). + ### iOS Create an instance of the engine and enhance audio: @@ -265,7 +310,6 @@ for (;;) { Replace `${ACCESS_KEY}` with yours obtained from [Picovoice Console](https://console.picovoice.ai/). Finally, when done release the resources using `koala.release()`. - ## Releases ### v1.0.0 February 7th, 2023 diff --git a/binding/android/.gitignore b/binding/android/.gitignore new file mode 100644 index 0000000..f70b309 --- /dev/null +++ b/binding/android/.gitignore @@ -0,0 +1,12 @@ +*.iml +.gradle +/local.properties +/.idea/ +.DS_Store +/build +/captures +test_resources +.settings +.classpath +.project +publish-mavencentral.gradle \ No newline at end of file diff --git a/binding/android/README.md b/binding/android/README.md new file mode 100644 index 0000000..0ed1445 --- /dev/null +++ b/binding/android/README.md @@ -0,0 +1,80 @@ +# Koala Binding for Android + +## Koala Noise Suppression Engine + +Made in Vancouver, Canada by [Picovoice](https://picovoice.ai) + +Koala is an on-device noise suppression engine. Koala is: + +- Private; All voice processing runs locally. +- Cross-Platform: + - Linux (x86_64), macOS (x86_64, arm64), Windows (x86_64) + - Android and iOS + - Chrome, Safari, Firefox, and Edge + - Raspberry Pi (4, 3) and NVIDIA Jetson Nano + +## Compatibility + +- Android 5.0 (SDK 21+) + +## Installation + +Koala is hosted on Maven Central. To include the package in your Android project, ensure you have +included `mavenCentral()` in your top-level `build.gradle` file and then add the following to your +app's `build.gradle`: + +```groovy +dependencies { + // ... + implementation 'ai.picovoice:koala-android:${VERSION}' +} +``` + +## AccessKey + +Koala requires a valid Picovoice `AccessKey` at initialization. `AccessKey` acts as your credentials when using Koala SDKs. +You can get your `AccessKey` for free. Make sure to keep your `AccessKey` secret. +Signup or Login to [Picovoice Console](https://console.picovoice.ai/) to get your `AccessKey`. + +## Usage + +Create an instance of the engine with the Koala Builder class by passing in the `accessKey` and Android app context: + +```java +import ai.picovoice.koala.*; + +final String accessKey = "${ACCESS_KEY}"; // AccessKey provided by Picovoice Console (https://console.picovoice.ai/) +try { + Koala koala = new Koala.Builder() + .setAccessKey(accessKey) + .build(appContext); +} catch (KoalaException ex) { } +``` + +Enhance audio: + +```java +short[] getNextAudioFrame() { + // .. get audioFrame + return audioFrame; +} + +while true { + short[] enhancedFrame = koala.process(getNextAudioFrame()); +} +``` + +In case the next audio frame does not directly follow the previous one, call `reset()`: +```java +koala.reset(); +``` + +When done, resources have to be released explicitly: + +```java +koala.delete(); +``` + +## Demo App + +For example usage refer to our [Android demo application](../../demo/android). diff --git a/binding/android/build.gradle b/binding/android/build.gradle new file mode 100644 index 0000000..073fc63 --- /dev/null +++ b/binding/android/build.gradle @@ -0,0 +1,25 @@ +ext { + defaultTargetSdkVersion = 31 +} + +buildscript { + repositories { + maven { url "https://plugins.gradle.org/m2/" } + google() + mavenCentral() + } + dependencies { + classpath "com.android.tools.build:gradle:4.2.2" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/binding/android/gradle.properties b/binding/android/gradle.properties new file mode 100644 index 0000000..6826e61 --- /dev/null +++ b/binding/android/gradle.properties @@ -0,0 +1,17 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true \ No newline at end of file diff --git a/binding/android/gradle/wrapper/gradle-wrapper.jar b/binding/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and b/binding/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/binding/android/gradle/wrapper/gradle-wrapper.properties b/binding/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1659a76 --- /dev/null +++ b/binding/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Jun 29 22:27:49 PDT 2021 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/binding/android/gradlew b/binding/android/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/binding/android/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# 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 +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +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" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/binding/android/gradlew.bat b/binding/android/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/binding/android/gradlew.bat @@ -0,0 +1,84 @@ +@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 + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@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= + +@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 Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_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=%* + +: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/binding/android/koala/.gitignore b/binding/android/koala/.gitignore new file mode 100644 index 0000000..d4681e4 --- /dev/null +++ b/binding/android/koala/.gitignore @@ -0,0 +1,3 @@ +/build +src/main/jniLibs/**/*.so +src/main/res/**/*.pv diff --git a/binding/android/koala/build.gradle b/binding/android/koala/build.gradle new file mode 100644 index 0000000..0f58d1d --- /dev/null +++ b/binding/android/koala/build.gradle @@ -0,0 +1,53 @@ +apply plugin: 'com.android.library' + +ext { + PUBLISH_GROUP_ID = 'ai.picovoice' + PUBLISH_VERSION = '1.0.0' + PUBLISH_ARTIFACT_ID = 'koala-android' +} + +android { + compileSdkVersion defaultTargetSdkVersion + buildToolsVersion "30.0.3" + + defaultConfig { + minSdkVersion 21 + targetSdkVersion defaultTargetSdkVersion + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +if (file("${rootDir}/publish-mavencentral.gradle").exists()) { + apply from: "${rootDir}/publish-mavencentral.gradle" +} + +dependencies { + implementation fileTree(dir: "libs", include: ["*.jar"]) +} + +task copyLibs(type: Copy) { + from("${rootDir}/../../lib/android") + into("${rootDir}/koala/src/main/jniLibs") +} + +task copyParams(type: Copy) { + from("${rootDir}/../../lib/common") + into("${rootDir}/koala/src/main/res/raw") +} + +preBuild.dependsOn copyLibs +preBuild.dependsOn copyParams diff --git a/binding/android/koala/consumer-rules.pro b/binding/android/koala/consumer-rules.pro new file mode 100644 index 0000000..2f2129e --- /dev/null +++ b/binding/android/koala/consumer-rules.pro @@ -0,0 +1 @@ +-keep class ai.picovoice.koala.*Exception { (...); } diff --git a/binding/android/koala/proguard-rules.pro b/binding/android/koala/proguard-rules.pro new file mode 100644 index 0000000..2f2129e --- /dev/null +++ b/binding/android/koala/proguard-rules.pro @@ -0,0 +1 @@ +-keep class ai.picovoice.koala.*Exception { (...); } diff --git a/binding/android/koala/src/main/AndroidManifest.xml b/binding/android/koala/src/main/AndroidManifest.xml new file mode 100644 index 0000000..312eeed --- /dev/null +++ b/binding/android/koala/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/binding/android/koala/src/main/java/ai/picovoice/koala/Koala.java b/binding/android/koala/src/main/java/ai/picovoice/koala/Koala.java new file mode 100644 index 0000000..12083d3 --- /dev/null +++ b/binding/android/koala/src/main/java/ai/picovoice/koala/Koala.java @@ -0,0 +1,221 @@ +/* + Copyright 2023 Picovoice Inc. + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. +*/ + + +package ai.picovoice.koala; + +import android.content.Context; +import android.content.res.Resources; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Android binding for Koala Noise Suppression engine. + */ +public class Koala { + + private static String defaultModelPath; + + static { + System.loadLibrary("pv_koala"); + } + + private long handle; + + /** + * Constructor. + * + * @param accessKey AccessKey obtained from Picovoice Console + * @param modelPath Absolute path to the file containing Koala model parameters. + * + * @throws KoalaException if there is an error while initializing Koala. + */ + private Koala(String accessKey, String modelPath) throws KoalaException { + handle = KoalaNative.init(accessKey, modelPath); + } + + private static void extractPackageResources(Context context) throws KoalaException { + final Resources resources = context.getResources(); + + try { + defaultModelPath = extractResource(context, + resources.openRawResource(R.raw.koala_params), + resources.getResourceEntryName(R.raw.koala_params) + ".pv"); + } catch (IOException ex) { + throw new KoalaIOException(ex); + } + } + + private static String extractResource(Context context, InputStream srcFileStream, String dstFilename) throws IOException { + InputStream is = new BufferedInputStream(srcFileStream, 256); + OutputStream os = new BufferedOutputStream(context.openFileOutput(dstFilename, Context.MODE_PRIVATE), 256); + int r; + while ((r = is.read()) != -1) { + os.write(r); + } + os.flush(); + + is.close(); + os.close(); + return new File(context.getFilesDir(), dstFilename).getAbsolutePath(); + } + + /** + * Releases resources acquired by Koala. + */ + public void delete() { + if (handle != 0) { + KoalaNative.delete(handle); + handle = 0; + } + } + + /** + * Processes given audio data and returns delayed enhanced audio. + * + * @param pcm A frame of audio samples. The number of samples per frame can be obtained by + * calling {@link #getFrameLength()}. The incoming audio needs to have a sample rate + * equal to {@link #getSampleRate()} and be 16-bit linearly-encoded. Koala operates + * on single-channel audio. Consecutive calls to {@link #process()} must provide consecutive + * frames of audio from the same source, unless {@link #reset()} has been called in between. + * + * @return A frame of enhanced audio samples, stored as a sequence of 16-bit linearly-encoded integers. + * The output is not directly the enhanced version of the input PCM, but corresponds to samples + * that were given in previous calls to {@link #process()}. The delay in samples between the start + * time of the input frame and the start time of the output frame can be obtained from {@link #getDelaySample()}. + * + * @throws KoalaException if there is an error while processing the audio frame. + */ + public short[] process(short[] pcm) throws KoalaException { + if (handle == 0) { + throw new KoalaInvalidStateException("Attempted to call Koala process after delete."); + } + + if (pcm == null) { + throw new KoalaInvalidArgumentException("Passed null frame to Koala process."); + } + + if (pcm.length != getFrameLength()) { + throw new KoalaInvalidArgumentException( + String.format("Koala process requires frames of length %d. " + + "Received frame of size %d.", getFrameLength(), pcm.length)); + } + return KoalaNative.process(handle, pcm); + } + + /** + * Resets Koala into a state as if it had just been newly created. + * Call this function in between calls to {@link #process()} that do not + * provide consecutive frames of audio. + * + * + * @throws KoalaException if there is an error while processing the audio frame. + */ + public void reset() throws KoalaException { + if (handle == 0) { + throw new KoalaInvalidStateException("Attempted to call Koala reset after delete."); + } + KoalaNative.reset(handle); + } + + /** + * Getter for required number of audio samples per frame. + * + * @return Required number of audio samples per frame. + */ + public int getFrameLength() { + return KoalaNative.getFrameLength(); + } + + /** + * Getter for required audio sample rate for PCM data. + * + * @return Required audio sample rate for PCM data. + */ + public int getSampleRate() { + return KoalaNative.getSampleRate(); + } + + /** + * Getter for Koala version. + * + * @return Koala version. + */ + public String getVersion() { + return KoalaNative.getVersion(); + } + + /** + * Getter for Koala delaySample. + * + * @return Koala process delay in number of samples. + */ + public int getDelaySample() throws KoalaException { + return KoalaNative.delaySample(handle); + } + + public static class Builder { + + private String accessKey = null; + private String modelPath = null; + + /** + * Setter for the AccessKey + * + * @param accessKey AccessKey obtained from Picovoice Console + */ + public Builder setAccessKey(String accessKey) { + this.accessKey = accessKey; + return this; + } + + /** + * Setter for the absolute path to the file containing Koala model parameters. + * + * @param modelPath Absolute path to the file containing Koala model parameters. + */ + public Builder setModelPath(String modelPath) { + this.modelPath = modelPath; + return this; + } + + public Koala build(Context context) throws KoalaException { + if (accessKey == null || this.accessKey.equals("")) { + throw new KoalaInvalidArgumentException("No AccessKey was provided to Koala"); + } + + if (modelPath == null) { + if (defaultModelPath == null) { + extractPackageResources(context); + } + modelPath = defaultModelPath; + } else { + File modelFile = new File(modelPath); + String modelFilename = modelFile.getName(); + if (!modelFile.exists() && !modelFilename.equals("")) { + try { + modelPath = extractResource(context, + context.getAssets().open(modelPath), + modelFilename); + } catch (IOException ex) { + throw new KoalaIOException(ex); + } + } + } + + return new Koala(accessKey, modelPath); + } + } +} diff --git a/binding/android/koala/src/main/java/ai/picovoice/koala/KoalaNative.java b/binding/android/koala/src/main/java/ai/picovoice/koala/KoalaNative.java new file mode 100644 index 0000000..c954efa --- /dev/null +++ b/binding/android/koala/src/main/java/ai/picovoice/koala/KoalaNative.java @@ -0,0 +1,33 @@ +/* + Copyright 2023 Picovoice Inc. + + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ai.picovoice.koala; + +class KoalaNative { + + static native int getSampleRate(); + + static native String getVersion(); + + static native int getFrameLength(); + + static native long init(String accessKey, String modelPath) throws KoalaException; + + static native void delete(long object); + + static native short[] process(long object, short[] pcm) throws KoalaException; + + static native void reset(long object) throws KoalaException; + + static native int delaySample(long object) throws KoalaException; + +} diff --git a/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationException.java b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationException.java new file mode 100644 index 0000000..1b3138d --- /dev/null +++ b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationException.java @@ -0,0 +1,22 @@ +/* + Copyright 2023 Picovoice Inc. + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ai.picovoice.koala; + +public class KoalaActivationException extends KoalaException { + public KoalaActivationException(Throwable cause) { + super(cause); + } + + public KoalaActivationException(String message) { + super(message); + } +} + diff --git a/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationLimitException.java b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationLimitException.java new file mode 100644 index 0000000..c04c005 --- /dev/null +++ b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationLimitException.java @@ -0,0 +1,22 @@ +/* + Copyright 2023 Picovoice Inc. + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ai.picovoice.koala; + +public class KoalaActivationLimitException extends KoalaException { + public KoalaActivationLimitException(Throwable cause) { + super(cause); + } + + public KoalaActivationLimitException(String message) { + super(message); + } +} + diff --git a/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationRefusedException.java b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationRefusedException.java new file mode 100644 index 0000000..d9ff09e --- /dev/null +++ b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationRefusedException.java @@ -0,0 +1,22 @@ +/* + Copyright 2023 Picovoice Inc. + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ai.picovoice.koala; + +public class KoalaActivationRefusedException extends KoalaException { + public KoalaActivationRefusedException(Throwable cause) { + super(cause); + } + + public KoalaActivationRefusedException(String message) { + super(message); + } +} + diff --git a/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationThrottledException.java b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationThrottledException.java new file mode 100644 index 0000000..b50d1a5 --- /dev/null +++ b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationThrottledException.java @@ -0,0 +1,22 @@ +/* + Copyright 2023 Picovoice Inc. + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ai.picovoice.koala; + +public class KoalaActivationThrottledException extends KoalaException { + public KoalaActivationThrottledException(Throwable cause) { + super(cause); + } + + public KoalaActivationThrottledException(String message) { + super(message); + } +} + diff --git a/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaException.java b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaException.java new file mode 100644 index 0000000..6042dd4 --- /dev/null +++ b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaException.java @@ -0,0 +1,22 @@ +/* + Copyright 2023 Picovoice Inc. + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ai.picovoice.koala; + +public class KoalaException extends Exception { + public KoalaException(Throwable cause) { + super(cause); + } + + public KoalaException(String message) { + super(message); + } +} + diff --git a/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaIOException.java b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaIOException.java new file mode 100644 index 0000000..ba2b3e0 --- /dev/null +++ b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaIOException.java @@ -0,0 +1,22 @@ +/* + Copyright 2023 Picovoice Inc. + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ai.picovoice.koala; + +public class KoalaIOException extends KoalaException { + public KoalaIOException(Throwable cause) { + super(cause); + } + + public KoalaIOException(String message) { + super(message); + } +} + diff --git a/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaInvalidArgumentException.java b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaInvalidArgumentException.java new file mode 100644 index 0000000..4abc938 --- /dev/null +++ b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaInvalidArgumentException.java @@ -0,0 +1,22 @@ +/* + Copyright 2023 Picovoice Inc. + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ai.picovoice.koala; + +public class KoalaInvalidArgumentException extends KoalaException { + public KoalaInvalidArgumentException(Throwable cause) { + super(cause); + } + + public KoalaInvalidArgumentException(String message) { + super(message); + } +} + diff --git a/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaInvalidStateException.java b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaInvalidStateException.java new file mode 100644 index 0000000..9d80896 --- /dev/null +++ b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaInvalidStateException.java @@ -0,0 +1,22 @@ +/* + Copyright 2023 Picovoice Inc. + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ai.picovoice.koala; + +public class KoalaInvalidStateException extends KoalaException { + public KoalaInvalidStateException(Throwable cause) { + super(cause); + } + + public KoalaInvalidStateException(String message) { + super(message); + } +} + diff --git a/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaKeyException.java b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaKeyException.java new file mode 100644 index 0000000..5215661 --- /dev/null +++ b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaKeyException.java @@ -0,0 +1,22 @@ +/* + Copyright 2023 Picovoice Inc. + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ai.picovoice.koala; + +public class KoalaKeyException extends KoalaException { + public KoalaKeyException(Throwable cause) { + super(cause); + } + + public KoalaKeyException(String message) { + super(message); + } +} + diff --git a/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaMemoryException.java b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaMemoryException.java new file mode 100644 index 0000000..b5d7a63 --- /dev/null +++ b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaMemoryException.java @@ -0,0 +1,22 @@ +/* + Copyright 2023 Picovoice Inc. + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ai.picovoice.koala; + +public class KoalaMemoryException extends KoalaException { + public KoalaMemoryException(Throwable cause) { + super(cause); + } + + public KoalaMemoryException(String message) { + super(message); + } +} + diff --git a/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaRuntimeException.java b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaRuntimeException.java new file mode 100644 index 0000000..d1dba66 --- /dev/null +++ b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaRuntimeException.java @@ -0,0 +1,22 @@ +/* + Copyright 2023 Picovoice Inc. + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ai.picovoice.koala; + +public class KoalaRuntimeException extends KoalaException { + public KoalaRuntimeException(Throwable cause) { + super(cause); + } + + public KoalaRuntimeException(String message) { + super(message); + } +} + diff --git a/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaStopIterationException.java b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaStopIterationException.java new file mode 100644 index 0000000..595d14d --- /dev/null +++ b/binding/android/koala/src/main/java/ai/picovoice/koala/exception/KoalaStopIterationException.java @@ -0,0 +1,22 @@ +/* + Copyright 2023 Picovoice Inc. + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ai.picovoice.koala; + +public class KoalaStopIterationException extends KoalaException { + public KoalaStopIterationException(Throwable cause) { + super(cause); + } + + public KoalaStopIterationException(String message) { + super(message); + } +} + diff --git a/binding/android/settings.gradle b/binding/android/settings.gradle new file mode 100644 index 0000000..98eee24 --- /dev/null +++ b/binding/android/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = "Koala" +include ':koala' diff --git a/demo/android/.gitignore b/demo/android/.gitignore new file mode 100644 index 0000000..3f98908 --- /dev/null +++ b/demo/android/.gitignore @@ -0,0 +1,18 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +.settings +.classpath +.project \ No newline at end of file diff --git a/demo/android/Activity/.gitignore b/demo/android/Activity/.gitignore new file mode 100644 index 0000000..9b41607 --- /dev/null +++ b/demo/android/Activity/.gitignore @@ -0,0 +1,10 @@ +*.iml +.gradle +/local.properties +/.idea/ +.DS_Store +/build +/captures +.externalNativeBuild +release +test_resources \ No newline at end of file diff --git a/demo/android/Activity/README.md b/demo/android/Activity/README.md new file mode 100644 index 0000000..4e4824d --- /dev/null +++ b/demo/android/Activity/README.md @@ -0,0 +1,22 @@ +# Android Activity Demo + +This Android demo runs Koala in the foreground (i.e., when the app is in focus). + +## Setup + +1. Replace `String ACCESS_KEY = "..."` inside + [MainActivity.java](koala-activity-demo-app/src/main/java/ai/picovoice/koalaactivitydemo/MainActivity.java) + with your AccessKey generated by [Picovoice Console](https://console.picovoice.ai/). + +```java +private static final String ACCESS_KEY = "YOUR_ACCESS_KEY_HERE"; +``` + +2. Add `pvTestingAccessKey = "..."` inside `local.properties` + with your AccessKey generated by [Picovoice Console](https://console.picovoice.ai/). + +```console +pvTestingAccessKey=YOUR_ACCESS_KEY_HERE +``` + +3. Open the project in Android Studio and run the demo on your device or simulator. diff --git a/demo/android/Activity/build.gradle b/demo/android/Activity/build.gradle new file mode 100644 index 0000000..23605a6 --- /dev/null +++ b/demo/android/Activity/build.gradle @@ -0,0 +1,28 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +ext { + defaultTargetSdkVersion = 31 +} + +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:7.4.1' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/demo/android/Activity/copy_test_resources.sh b/demo/android/Activity/copy_test_resources.sh new file mode 100755 index 0000000..a7120f5 --- /dev/null +++ b/demo/android/Activity/copy_test_resources.sh @@ -0,0 +1,9 @@ +if [ ! -d "./koala-activity-demo-app/src/androidTest/assets/test_resources/audio" ] +then + echo "Creating test audio samples directory..." + mkdir -p ./koala-activity-demo-app/src/androidTest/assets/test_resources/audio +fi + +echo "Copying test audio samples..." +cp ../../../resources/audio_samples/test.wav ./koala-activity-demo-app/src/androidTest/assets/test_resources/audio/test.wav +cp ../../../resources/audio_samples/noise.wav ./koala-activity-demo-app/src/androidTest/assets/test_resources/audio/noise.wav diff --git a/demo/android/Activity/gradle.properties b/demo/android/Activity/gradle.properties new file mode 100644 index 0000000..c09e1e3 --- /dev/null +++ b/demo/android/Activity/gradle.properties @@ -0,0 +1,17 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true diff --git a/demo/android/Activity/gradle/wrapper/gradle-wrapper.jar b/demo/android/Activity/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and b/demo/android/Activity/gradle/wrapper/gradle-wrapper.jar differ diff --git a/demo/android/Activity/gradle/wrapper/gradle-wrapper.properties b/demo/android/Activity/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3fadc5c --- /dev/null +++ b/demo/android/Activity/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Jun 29 23:02:09 PDT 2021 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/demo/android/Activity/gradlew b/demo/android/Activity/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/demo/android/Activity/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# 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 +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +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" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/demo/android/Activity/gradlew.bat b/demo/android/Activity/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/demo/android/Activity/gradlew.bat @@ -0,0 +1,84 @@ +@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 + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@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= + +@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 Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_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=%* + +: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/demo/android/Activity/koala-activity-demo-app/.gitignore b/demo/android/Activity/koala-activity-demo-app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/demo/android/Activity/koala-activity-demo-app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/demo/android/Activity/koala-activity-demo-app/build.gradle b/demo/android/Activity/koala-activity-demo-app/build.gradle new file mode 100644 index 0000000..e64f69c --- /dev/null +++ b/demo/android/Activity/koala-activity-demo-app/build.gradle @@ -0,0 +1,78 @@ +plugins { + id 'com.android.application' +} + +Properties properties = new Properties() +if (rootProject.file("local.properties").exists()) { + properties.load(rootProject.file("local.properties").newDataInputStream()) + if (project.hasProperty("pvTestingAccessKey")) { + properties.put("pvTestingAccessKey", project.getProperty("pvTestingAccessKey")) + } + if (project.hasProperty("numTestIterations")) { + properties.put("numTestIterations", project.getProperty("numTestIterations")) + } + if (project.hasProperty("performanceThresholdSec")) { + properties.put("performanceThresholdSec", project.getProperty("performanceThresholdSec")) + } +} + +android { + compileSdkVersion defaultTargetSdkVersion + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "ai.picovoice.koalaactivitydemo" + minSdkVersion 21 + targetSdkVersion defaultTargetSdkVersion + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + resValue 'string', 'pvTestingAccessKey', properties.getProperty("pvTestingAccessKey", "") + resValue 'string', 'numTestIterations', properties.getProperty("numTestIterations", "") + resValue 'string', 'performanceThresholdSec', properties.getProperty("performanceThresholdSec", "") + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + sourceSets { + androidTest { + java { + System.out.println() + if (properties.getProperty("performanceThresholdSec", "").length() == 0) { + exclude "**/PerformanceTest.java" + } else { + exclude "**/KoalaTest.java" + } + } + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + buildFeatures { + viewBinding true + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.1' + implementation 'androidx.navigation:navigation-fragment:2.3.5' + implementation 'androidx.navigation:navigation-ui:2.3.5' + implementation 'ai.picovoice:koala-android:1.0.0' + + // Espresso UI Testing + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation('androidx.test.espresso:espresso-core:3.2.0', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + + androidTestImplementation('com.microsoft.appcenter:espresso-test-extension:1.4') +} diff --git a/demo/android/Activity/koala-activity-demo-app/proguard-rules.pro b/demo/android/Activity/koala-activity-demo-app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/demo/android/Activity/koala-activity-demo-app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/demo/android/Activity/koala-activity-demo-app/src/androidTest/java/ai/picovoice/koalaactivitydemo/KoalaTest.java b/demo/android/Activity/koala-activity-demo-app/src/androidTest/java/ai/picovoice/koalaactivitydemo/KoalaTest.java new file mode 100644 index 0000000..0e70305 --- /dev/null +++ b/demo/android/Activity/koala-activity-demo-app/src/androidTest/java/ai/picovoice/koalaactivitydemo/KoalaTest.java @@ -0,0 +1,221 @@ +/* + Copyright 2023 Picovoice Inc. + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ai.picovoice.koalaactivitydemo; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; + +import android.content.Context; +import android.content.res.AssetManager; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.microsoft.appcenter.espresso.Factory; +import com.microsoft.appcenter.espresso.ReportHelper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.List; + +import ai.picovoice.koala.Koala; +import ai.picovoice.koala.KoalaException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + + +@RunWith(AndroidJUnit4.class) +public class KoalaTest { + + @Rule + public ReportHelper reportHelper = Factory.getReportHelper(); + Context testContext; + Context appContext; + AssetManager assetManager; + String testResourcesPath; + + String accessKey = ""; + + @After + public void TearDown() { + reportHelper.label("Stopping App"); + } + + @Before + public void Setup() throws IOException { + testContext = InstrumentationRegistry.getInstrumentation().getContext(); + appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assetManager = testContext.getAssets(); + extractAssetsRecursively("test_resources"); + testResourcesPath = new File(appContext.getFilesDir(), "test_resources").getAbsolutePath(); + + accessKey = appContext.getString(R.string.pvTestingAccessKey); + } + + private List loadPcm(File file) throws IOException { + FileInputStream inputStream = new FileInputStream(file); + inputStream.skip(44); + + List output = new ArrayList<>(); + + byte[] buffer = new byte[512 * 2]; + ByteBuffer pcmBuff = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN); + + while (inputStream.available() > 0) { + int numRead = inputStream.read(pcmBuff.array()); + ShortBuffer pcmShortBuffer = pcmBuff.asShortBuffer(); + for (int i = 0; i < (numRead / 2); i ++) { + output.add(pcmShortBuffer.get(i)); + } + } + return output; + } + + private double pcmRootMeanSquare(short[] frame) { + double sumOfSquares = 0; + for (short x : frame) { + sumOfSquares += Math.pow((x / 32768f), 2); + } + return Math.sqrt((sumOfSquares / frame.length)); + } + + private short[] frameFromList(List inputList, int start, int count) { + short[] output = new short[count]; + for(int i = 0; i < count; i++) { + output[i] = inputList.get(start + i); + } + return output; + } + + private void runTest(List inputPcm, List referencePcm, double tolerance) throws KoalaException { + Koala koala = new Koala.Builder().setAccessKey(accessKey).build(getApplicationContext()); + + for (int i = 0; i < (inputPcm.size() - koala.getFrameLength()); i += koala.getFrameLength()) { + short[] frame = frameFromList(inputPcm, i, koala.getFrameLength()); + short[] enhancedFrame = koala.process(frame); + + double energyDeviation; + double enhancedFrameEnergy = pcmRootMeanSquare(enhancedFrame); + if (referencePcm == null || i < koala.getDelaySample()) { + energyDeviation = enhancedFrameEnergy; + } else { + short[] referenceFrame = frameFromList(referencePcm, i - koala.getDelaySample(), koala.getFrameLength()); + double referenceFrameEnergy = pcmRootMeanSquare(referenceFrame); + energyDeviation = Math.abs(enhancedFrameEnergy - referenceFrameEnergy); + } + assertTrue(energyDeviation < tolerance); + } + } + + @Test + public void testPureSpeech() throws KoalaException, IOException { + List testPcm = loadPcm(new File(testResourcesPath, "audio/test.wav")); + runTest(testPcm, testPcm, 0.02); + } + + @Test + public void testPureNoise() throws KoalaException, IOException { + List noisePcm = loadPcm(new File(testResourcesPath, "audio/noise.wav")); + runTest(noisePcm, null, 0.02); + } + + @Test + public void testMixed() throws KoalaException, IOException { + List testPcm = loadPcm(new File(testResourcesPath, "audio/test.wav")); + List noisePcm = loadPcm(new File(testResourcesPath, "audio/noise.wav")); + List mixedPcm = new ArrayList<>(); + for (int i = 0; i < testPcm.size(); i++) { + Short mixed = (short) (testPcm.get(i) + noisePcm.get(i)); + mixedPcm.add(mixed); + } + runTest(mixedPcm, testPcm, 0.02); + } + + @Test + public void testReset() throws KoalaException, IOException { + Koala koala = new Koala.Builder().setAccessKey(accessKey).build(getApplicationContext()); + List testPcm = loadPcm(new File(testResourcesPath, "audio/test.wav")); + + List referenceFrames = new ArrayList<>(); + + for (int i = 0; i < (testPcm.size() - koala.getFrameLength()); i += koala.getFrameLength()) { + short[] inputFrame = frameFromList(testPcm, i, koala.getFrameLength()); + referenceFrames.add(koala.process(inputFrame)); + } + + koala.reset(); + + for (int i = 0; i < (testPcm.size() - koala.getFrameLength()); i += koala.getFrameLength()) { + short[] inputFrame = frameFromList(testPcm, i, koala.getFrameLength()); + short[] outputFrame = koala.process(inputFrame); + + short[] referenceFrame = referenceFrames.remove(0); + for (int j = 0; j < outputFrame.length; j++) { + assertEquals(outputFrame[j], referenceFrame[j]); + } + } + } + + @Test + public void testVersion() throws KoalaException { + Koala koala = new Koala.Builder().setAccessKey(accessKey).build(getApplicationContext()); + assertTrue(koala.getVersion().length() > 0); + } + + private void extractAssetsRecursively(String path) throws IOException { + + String[] list = assetManager.list(path); + if (list.length > 0) { + File outputFile = new File(appContext.getFilesDir(), path); + if (!outputFile.exists()) { + outputFile.mkdirs(); + } + + for (String file : list) { + String filepath = path + "/" + file; + extractAssetsRecursively(filepath); + } + } else { + extractTestFile(path); + } + } + + private void extractTestFile(String filepath) throws IOException { + + InputStream is = new BufferedInputStream(assetManager.open(filepath), 256); + File absPath = new File(appContext.getFilesDir(), filepath); + OutputStream os = new BufferedOutputStream(new FileOutputStream(absPath), 256); + int r; + while ((r = is.read()) != -1) { + os.write(r); + } + os.flush(); + + is.close(); + os.close(); + } +} diff --git a/demo/android/Activity/koala-activity-demo-app/src/androidTest/java/ai/picovoice/koalaactivitydemo/PerformanceTest.java b/demo/android/Activity/koala-activity-demo-app/src/androidTest/java/ai/picovoice/koalaactivitydemo/PerformanceTest.java new file mode 100644 index 0000000..ea521ff --- /dev/null +++ b/demo/android/Activity/koala-activity-demo-app/src/androidTest/java/ai/picovoice/koalaactivitydemo/PerformanceTest.java @@ -0,0 +1,147 @@ +/* + Copyright 2023 Picovoice Inc. + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. +*/ +package ai.picovoice.koalaactivitydemo; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static org.junit.Assert.assertTrue; + +import android.content.Context; +import android.content.res.AssetManager; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.microsoft.appcenter.espresso.Factory; +import com.microsoft.appcenter.espresso.ReportHelper; + +import org.junit.After; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import ai.picovoice.koala.Koala; + +@RunWith(AndroidJUnit4.class) +public class PerformanceTest { + @Rule + public ReportHelper reportHelper = Factory.getReportHelper(); + Context testContext; + Context appContext; + AssetManager assetManager; + String testResourcesPath; + String accessKey; + + @After + public void TearDown() { + reportHelper.label("Stopping App"); + } + + @Before + public void Setup() throws IOException { + testContext = InstrumentationRegistry.getInstrumentation().getContext(); + appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assetManager = testContext.getAssets(); + extractAssetsRecursively("test_resources"); + testResourcesPath = new File(appContext.getFilesDir(), "test_resources").getAbsolutePath(); + + accessKey = appContext.getString(R.string.pvTestingAccessKey); + } + + @Test + public void testPerformance() throws Exception { + String iterationString = appContext.getString(R.string.numTestIterations); + String thresholdString = appContext.getString(R.string.performanceThresholdSec); + Assume.assumeNotNull(thresholdString); + Assume.assumeFalse(thresholdString.equals("")); + + int numTestIterations = 100; + try { + numTestIterations = Integer.parseInt(iterationString); + } catch (NumberFormatException ignored) {} + double performanceThresholdSec = Double.parseDouble(thresholdString); + + Koala koala = new Koala.Builder().setAccessKey(accessKey).build(getApplicationContext()); + File testAudio = new File(testResourcesPath, "audio/test.wav"); + + long totalNSec = 0; + for (int i = 0; i < numTestIterations; i++) { + FileInputStream audioInputStream = new FileInputStream(testAudio); + + byte[] rawData = new byte[koala.getFrameLength() * 2]; + short[] pcm = new short[koala.getFrameLength()]; + ByteBuffer pcmBuff = ByteBuffer.wrap(rawData).order(ByteOrder.LITTLE_ENDIAN); + + audioInputStream.skip(44); + + while (audioInputStream.available() > 0) { + int numRead = audioInputStream.read(pcmBuff.array()); + if (numRead == koala.getFrameLength() * 2) { + pcmBuff.asShortBuffer().get(pcm); + long before = System.nanoTime(); + koala.process(pcm); + long after = System.nanoTime(); + totalNSec += after - before; + } + } + } + koala.delete(); + + double avgNSec = totalNSec / (double) numTestIterations; + double avgSec = ((double) Math.round(avgNSec * 1e-6)) / 1000.0; + assertTrue( + String.format("Expected threshold (%.3fs), process took (%.3fs)", performanceThresholdSec, avgSec), + avgSec <= performanceThresholdSec + ); + } + + private void extractAssetsRecursively(String path) throws IOException { + String[] list = assetManager.list(path); + if (list.length > 0) { + File outputFile = new File(appContext.getFilesDir(), path); + if (!outputFile.exists()) { + outputFile.mkdirs(); + } + + for (String file : list) { + String filepath = path + "/" + file; + extractAssetsRecursively(filepath); + } + } else { + extractTestFile(path); + } + } + + private void extractTestFile(String filepath) throws IOException { + InputStream is = new BufferedInputStream(assetManager.open(filepath), 256); + File absPath = new File(appContext.getFilesDir(), filepath); + OutputStream os = new BufferedOutputStream(new FileOutputStream(absPath), 256); + int r; + while ((r = is.read()) != -1) { + os.write(r); + } + os.flush(); + + is.close(); + os.close(); + } +} diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/AndroidManifest.xml b/demo/android/Activity/koala-activity-demo-app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0f082a4 --- /dev/null +++ b/demo/android/Activity/koala-activity-demo-app/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/ic_launcher-playstore.png b/demo/android/Activity/koala-activity-demo-app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..0e7d9f6 Binary files /dev/null and b/demo/android/Activity/koala-activity-demo-app/src/main/ic_launcher-playstore.png differ diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/java/ai/picovoice/koalaactivitydemo/MainActivity.java b/demo/android/Activity/koala-activity-demo-app/src/main/java/ai/picovoice/koalaactivitydemo/MainActivity.java new file mode 100644 index 0000000..85862dd --- /dev/null +++ b/demo/android/Activity/koala-activity-demo-app/src/main/java/ai/picovoice/koalaactivitydemo/MainActivity.java @@ -0,0 +1,384 @@ +/* + Copyright 2023 Picovoice Inc. + + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + + Unless required by applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + express or implied. See the License for the specific language governing permissions and + limitations under the License. +*/ + + +package ai.picovoice.koalaactivitydemo; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.pm.PackageManager; +import android.media.AudioAttributes; +import android.media.AudioFormat; +import android.media.AudioRecord; +import android.media.MediaPlayer; +import android.media.MediaRecorder; +import android.os.Bundle; +import android.os.Process; +import android.view.View; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.ToggleButton; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; + +import ai.picovoice.koala.*; + +public class MainActivity extends AppCompatActivity implements OnSeekBarChangeListener { + private final MicrophoneReader microphoneReader = new MicrophoneReader(); + public Koala koala; + + private static final String ACCESS_KEY = "${YOUR_ACCESS_KEY_HERE}"; + + private ToggleButton recordButton; + private ToggleButton playStopButton; + private TextView recordedText; + private ConstraintLayout playbackArea; + + private String referenceFilepath; + private String enhancedFilepath; + private MediaPlayer referenceMediaPlayer; + private MediaPlayer enhancedMediaPlayer; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.koala_activity_demo); + + recordButton = findViewById(R.id.startButton); + playStopButton = findViewById(R.id.playStopButton); + recordedText = findViewById(R.id.recordedText); + playbackArea = findViewById(R.id.playbackArea); + + SeekBar faderSlider = findViewById(R.id.faderSlider); + faderSlider.setOnSeekBarChangeListener(this); + + recordedText.setText(""); + playbackArea.setVisibility(View.INVISIBLE); + + try { + koala = new Koala.Builder().setAccessKey(ACCESS_KEY).build(getApplicationContext()); + } catch (KoalaInvalidArgumentException e) { + onKoalaInitError(String.format("AccessKey '%s' is invalid", ACCESS_KEY)); + } catch (KoalaActivationException e) { + onKoalaInitError("AccessKey activation error"); + } catch (KoalaActivationLimitException e) { + onKoalaInitError("AccessKey reached its device limit"); + } catch (KoalaActivationRefusedException e) { + onKoalaInitError("AccessKey refused"); + } catch (KoalaActivationThrottledException e) { + onKoalaInitError("AccessKey has been throttled"); + } catch (KoalaException e) { + onKoalaInitError("Failed to initialize Koala " + e.getMessage()); + } + + referenceFilepath = getApplicationContext().getFileStreamPath("reference.wav").getAbsolutePath(); + enhancedFilepath = getApplicationContext().getFileStreamPath("enhanced.wav").getAbsolutePath(); + referenceMediaPlayer = new MediaPlayer(); + enhancedMediaPlayer = new MediaPlayer(); + referenceMediaPlayer.setVolume(0, 0); + enhancedMediaPlayer.setVolume(1, 1); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (referenceMediaPlayer != null) { + referenceMediaPlayer.release(); + } + if (enhancedMediaPlayer != null) { + enhancedMediaPlayer.release(); + } + koala.delete(); + } + + private void onKoalaInitError(String error) { + TextView errorMessage = findViewById(R.id.errorMessage); + errorMessage.setText(error); + errorMessage.setVisibility(View.VISIBLE); + + recordButton.setEnabled(false); + recordButton.setBackground(ContextCompat.getDrawable(this, R.drawable.button_disabled)); + } + + private void displayError(String message) { + Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); + } + + private boolean hasRecordPermission() { + return ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED; + } + + private void requestRecordPermission() { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, 0); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (grantResults.length == 0 || grantResults[0] == PackageManager.PERMISSION_DENIED) { + ToggleButton toggleButton = findViewById(R.id.startButton); + toggleButton.toggle(); + } else { + try { + microphoneReader.start(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + public void onClickRecord(View view) { + try { + if (recordButton.isChecked()) { + playbackArea.setVisibility(View.INVISIBLE); + playStopButton.setChecked(false); + + if (referenceMediaPlayer.isPlaying()) { + referenceMediaPlayer.stop(); + } + if (enhancedMediaPlayer.isPlaying()) { + enhancedMediaPlayer.stop(); + } + + if (hasRecordPermission()) { + microphoneReader.start(); + } else { + requestRecordPermission(); + } + } else { + microphoneReader.stop(); + + resetMediaPlayer(referenceMediaPlayer, referenceFilepath); + resetMediaPlayer(enhancedMediaPlayer, enhancedFilepath); + + playbackArea.setVisibility(View.VISIBLE); + } + } catch (InterruptedException | IOException e) { + displayError("Audio stop command interrupted\n" + e.getMessage()); + } + } + + private void resetMediaPlayer(MediaPlayer mediaPlayer, String audioFile) throws IOException { + mediaPlayer.reset(); + mediaPlayer.setAudioAttributes( + new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .setUsage(AudioAttributes.USAGE_MEDIA) + .build() + ); + mediaPlayer.setLooping(true); + mediaPlayer.setDataSource(audioFile); + mediaPlayer.prepare(); + } + + public void onClickPlay(View view) { + if (playStopButton.isChecked()) { + referenceMediaPlayer.start(); + enhancedMediaPlayer.start(); + } else { + referenceMediaPlayer.pause(); + enhancedMediaPlayer.pause(); + referenceMediaPlayer.seekTo(0); + enhancedMediaPlayer.seekTo(0); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if(seekBar.getId() == R.id.faderSlider){ + float progressFloat = (float) progress; + float referenceVol = (100f - progressFloat) / 100f; + float enhancedVol = progressFloat / 100f; + + referenceMediaPlayer.setVolume(referenceVol, referenceVol); + enhancedMediaPlayer.setVolume(enhancedVol, enhancedVol); + } + } + + private class MicrophoneReader { + private final AtomicBoolean started = new AtomicBoolean(false); + private final AtomicBoolean stop = new AtomicBoolean(false); + private final AtomicBoolean stopped = new AtomicBoolean(false); + + final int wavHeaderLength = 44; + private RandomAccessFile referenceFile; + private RandomAccessFile enhancedFile; + private int totalSamplesWritten; + + + void start() throws IOException { + + if (started.get()) { + return; + } + + referenceFile = new RandomAccessFile(referenceFilepath, "rws"); + enhancedFile = new RandomAccessFile(enhancedFilepath, "rws"); + writeWavHeader(referenceFile, (short) 1, (short) 16, 16000, 0); + writeWavHeader(enhancedFile, (short) 1, (short) 16, 16000, 0); + + started.set(true); + + Executors.newSingleThreadExecutor().submit((Callable) () -> { + Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO); + read(); + return null; + }); + } + + void stop() throws InterruptedException, IOException { + if (!started.get()) { + return; + } + + stop.set(true); + + while (!stopped.get()) { + Thread.sleep(10); + } + + writeWavHeader(referenceFile, (short) 1, (short) 16, 16000, totalSamplesWritten); + writeWavHeader(enhancedFile, (short) 1, (short) 16, 16000, totalSamplesWritten); + referenceFile.close(); + enhancedFile.close(); + + started.set(false); + stop.set(false); + stopped.set(false); + } + + @SuppressLint({"MissingPermission", "SetTextI18n", "DefaultLocale"}) + private void read() throws KoalaException { + final int minBufferSize = AudioRecord.getMinBufferSize( + koala.getSampleRate(), + AudioFormat.CHANNEL_IN_MONO, + AudioFormat.ENCODING_PCM_16BIT); + final int bufferSize = Math.max(koala.getSampleRate() / 2, minBufferSize); + + AudioRecord audioRecord = null; + + short[] frameBuffer = new short[koala.getFrameLength()]; + + try { + audioRecord = new AudioRecord( + MediaRecorder.AudioSource.MIC, + koala.getSampleRate(), + AudioFormat.CHANNEL_IN_MONO, + AudioFormat.ENCODING_PCM_16BIT, + bufferSize); + audioRecord.startRecording(); + + final int koalaDelay = koala.getDelaySample(); + + totalSamplesWritten = 0; + int enhancedSamplesWritten = 0; + while (!stop.get()) { + if (audioRecord.read(frameBuffer, 0, frameBuffer.length) == frameBuffer.length) { + final short[] frameBufferEnhanced = koala.process(frameBuffer); + + writeFrame(referenceFile, frameBuffer); + totalSamplesWritten += frameBuffer.length; + if (totalSamplesWritten >= koalaDelay) { + writeFrame(enhancedFile, frameBufferEnhanced); + enhancedSamplesWritten += frameBufferEnhanced.length; + } + } + + if ((totalSamplesWritten / koala.getFrameLength()) % 10 == 0) { + runOnUiThread(() -> { + double secondsRecorded = ((double) (totalSamplesWritten) / (double) (koala.getSampleRate())); + recordedText.setText(String.format("Recording: %.1fs", secondsRecorded)); + }); + } + } + + audioRecord.stop(); + + runOnUiThread(() -> { + double secondsRecorded = ((double) (totalSamplesWritten) / (double) (koala.getSampleRate())); + recordedText.setText(String.format("Recorded: %.1fs", secondsRecorded)); + }); + + short[] emptyFrame = new short[koala.getFrameLength()]; + Arrays.fill(emptyFrame, (short) 0); + while (enhancedSamplesWritten < totalSamplesWritten) { + final short[] frameBufferEnhanced = koala.process(emptyFrame); + writeFrame(enhancedFile, frameBufferEnhanced); + enhancedSamplesWritten += frameBufferEnhanced.length; + } + } catch (IllegalArgumentException | IllegalStateException | IOException e) { + throw new KoalaException(e); + } finally { + if (audioRecord != null) { + audioRecord.release(); + } + stopped.set(true); + } + } + + private void writeFrame(RandomAccessFile outputFile, short[] frame) throws IOException { + ByteBuffer byteBuf = ByteBuffer.allocate(2 * frame.length); + byteBuf.order(ByteOrder.LITTLE_ENDIAN); + + for (short s : frame) { + byteBuf.putShort(s); + } + outputFile.write(byteBuf.array()); + } + + private void writeWavHeader(RandomAccessFile outputFile, short channelCount, short bitDepth, int sampleRate, int totalSampleCount) throws IOException { + ByteBuffer byteBuf = ByteBuffer.allocate(wavHeaderLength); + byteBuf.order(ByteOrder.LITTLE_ENDIAN); + + byteBuf.put("RIFF".getBytes(StandardCharsets.US_ASCII)); + byteBuf.putInt((bitDepth / 8 * totalSampleCount) + 36); + byteBuf.put("WAVE".getBytes(StandardCharsets.US_ASCII)); + byteBuf.put("fmt ".getBytes(StandardCharsets.US_ASCII)); + byteBuf.putInt(16); + byteBuf.putShort((short) 1); + byteBuf.putShort(channelCount); + byteBuf.putInt(sampleRate); + byteBuf.putInt(sampleRate * channelCount * bitDepth / 8); + byteBuf.putShort((short) (channelCount * bitDepth / 8)); + byteBuf.putShort(bitDepth); + byteBuf.put("data".getBytes(StandardCharsets.US_ASCII)); + byteBuf.putInt(bitDepth / 8 * totalSampleCount); + + outputFile.seek(0); + outputFile.write(byteBuf.array()); + } + } +} diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/drawable/button_background.xml b/demo/android/Activity/koala-activity-demo-app/src/main/res/drawable/button_background.xml new file mode 100644 index 0000000..7e73f1a --- /dev/null +++ b/demo/android/Activity/koala-activity-demo-app/src/main/res/drawable/button_background.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/drawable/button_disabled.xml b/demo/android/Activity/koala-activity-demo-app/src/main/res/drawable/button_disabled.xml new file mode 100644 index 0000000..ffe1c93 --- /dev/null +++ b/demo/android/Activity/koala-activity-demo-app/src/main/res/drawable/button_disabled.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/drawable/error_view.xml b/demo/android/Activity/koala-activity-demo-app/src/main/res/drawable/error_view.xml new file mode 100644 index 0000000..953beb3 --- /dev/null +++ b/demo/android/Activity/koala-activity-demo-app/src/main/res/drawable/error_view.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/drawable/ic_launcher_background.xml b/demo/android/Activity/koala-activity-demo-app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..196e181 --- /dev/null +++ b/demo/android/Activity/koala-activity-demo-app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,10 @@ + + + + diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/drawable/ic_launcher_foreground.xml b/demo/android/Activity/koala-activity-demo-app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..d2923ae --- /dev/null +++ b/demo/android/Activity/koala-activity-demo-app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/layout/koala_activity_demo.xml b/demo/android/Activity/koala-activity-demo-app/src/main/res/layout/koala_activity_demo.xml new file mode 100644 index 0000000..5187bf5 --- /dev/null +++ b/demo/android/Activity/koala-activity-demo-app/src/main/res/layout/koala_activity_demo.xml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..bbd3e02 --- /dev/null +++ b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..bbd3e02 --- /dev/null +++ b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-hdpi/ic_launcher.png b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..110f05f Binary files /dev/null and b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..60f69a7 Binary files /dev/null and b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-mdpi/ic_launcher.png b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..9b773de Binary files /dev/null and b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..0facebb Binary files /dev/null and b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-xhdpi/ic_launcher.png b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..e26e2af Binary files /dev/null and b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..d4d561d Binary files /dev/null and b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..54506ae Binary files /dev/null and b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..b089e59 Binary files /dev/null and b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..0e2c819 Binary files /dev/null and b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..16ae460 Binary files /dev/null and b/demo/android/Activity/koala-activity-demo-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/values/attrs.xml b/demo/android/Activity/koala-activity-demo-app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..e962673 --- /dev/null +++ b/demo/android/Activity/koala-activity-demo-app/src/main/res/values/attrs.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/values/colors.xml b/demo/android/Activity/koala-activity-demo-app/src/main/res/values/colors.xml new file mode 100644 index 0000000..30640a6 --- /dev/null +++ b/demo/android/Activity/koala-activity-demo-app/src/main/res/values/colors.xml @@ -0,0 +1,7 @@ + + + #377DFF + #25187E + #ff0e0e + #76838e + diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/values/strings.xml b/demo/android/Activity/koala-activity-demo-app/src/main/res/values/strings.xml new file mode 100644 index 0000000..ebe858b --- /dev/null +++ b/demo/android/Activity/koala-activity-demo-app/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + Koala Activity + Original + Koalafied + 0.8 + diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/res/values/styles.xml b/demo/android/Activity/koala-activity-demo-app/src/main/res/values/styles.xml new file mode 100644 index 0000000..db38c53 --- /dev/null +++ b/demo/android/Activity/koala-activity-demo-app/src/main/res/values/styles.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/demo/android/Activity/settings.gradle b/demo/android/Activity/settings.gradle new file mode 100644 index 0000000..e64fe9d --- /dev/null +++ b/demo/android/Activity/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = "KoalaActivityDemo" +include ':koala-activity-demo-app' diff --git a/demo/android/README.md b/demo/android/README.md new file mode 100644 index 0000000..10ef65f --- /dev/null +++ b/demo/android/README.md @@ -0,0 +1,13 @@ +# Android Demos + +## AccessKey + +Koala requires a valid Picovoice `AccessKey` at initialization. `AccessKey` acts as your credentials when using Koala SDKs. +You can get your `AccessKey` for free. Make sure to keep your `AccessKey` secret. +Signup or Login to [Picovoice Console](https://console.picovoice.ai/) to get your `AccessKey`. + +## Activity + +Copy your AccessKey into the `ACCESS_KEY` variable in `MainActivity.java` before building the demo. + +This demo is intended for applications that need to do speech enhancement when in focus. diff --git a/lib/android/arm64-v8a/libpv_koala.so b/lib/android/arm64-v8a/libpv_koala.so index 4ac8c75..bf43828 100755 Binary files a/lib/android/arm64-v8a/libpv_koala.so and b/lib/android/arm64-v8a/libpv_koala.so differ diff --git a/lib/android/armeabi-v7a/libpv_koala.so b/lib/android/armeabi-v7a/libpv_koala.so index 5d94a2c..8db97e6 100755 Binary files a/lib/android/armeabi-v7a/libpv_koala.so and b/lib/android/armeabi-v7a/libpv_koala.so differ diff --git a/lib/android/x86/libpv_koala.so b/lib/android/x86/libpv_koala.so index fb58c5e..d3b0670 100755 Binary files a/lib/android/x86/libpv_koala.so and b/lib/android/x86/libpv_koala.so differ diff --git a/lib/android/x86_64/libpv_koala.so b/lib/android/x86_64/libpv_koala.so index b6c525f..6de7777 100755 Binary files a/lib/android/x86_64/libpv_koala.so and b/lib/android/x86_64/libpv_koala.so differ diff --git a/resources/lint/spell-check/.cspell.json b/resources/lint/spell-check/.cspell.json index bc2ded3..d2a78d0 100644 --- a/resources/lint/spell-check/.cspell.json +++ b/resources/lint/spell-check/.cspell.json @@ -34,6 +34,20 @@ "**/*.storyboard", "**/*.pbxproj", "**/*.xcscheme", - "**/*.m" + "**/*.m", + "../../../lib/ios/**/*", + + // java + "**/build.gradle", + "**/gradle-wrapper.properties", + "**/gradle.properties", + "**/gradlew", + "**/gradlew.bat", + "**/proguard-rules.pro", + + // android + "**/AndroidManifest.xml", + "**/activity_main.xml", + "**/koala_activity_demo.xml" ] } diff --git a/resources/lint/spell-check/dict.txt b/resources/lint/spell-check/dict.txt index 5ee4d35..57768f3 100644 --- a/resources/lint/spell-check/dict.txt +++ b/resources/lint/spell-check/dict.txt @@ -1,11 +1,10 @@ -LPWSTR -MODLE -Makefiles aarch +androidx armv astype calcsize camelcase +compat copywasm denoising downsample @@ -17,10 +16,14 @@ hanning iife irfft jetson +koalaactivitydemo koalafied libpv linalg +LPWSTR +Makefiles malloc +MODLE ndarray numpy outf @@ -37,6 +40,7 @@ rfft rfftfreq signup sqrtf +styleable wargv wchars xcworkspace