Porcupine Binding for Android
Porcupine
Porcupine is is a highly accurate and lightweight wake word engine. It enables building always-listening voice-enabled applications using cutting edge voice AI.
Porcupine is:
- private and offline
- accurate
- resource efficient (runs even on microcontrollers)
- data efficient (wake words can be easily generated by simply typing them, without needing thousands of hours of bespoke audio training data and manual effort)
- scalable to many simultaneous wake-words / always-on voice commands
- cross-platform
To learn more about Porcupine, see the product, documentation, and GitHub pages.
Custom wake words
Porcupine includes several built-in keywords, which are stored as .ppn files. To train custom PPN files, see the Picovoice Console.
Unlike the built-in keywords, custom PPN files generated with the Picovoice Console carry restrictions including (but not limited to): training allowance, time limits, available platforms, and commercial usage.
Installation
Porcupine can be found 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:
dependencies {
// ...
implementation 'ai.picovoice:porcupine-android:${LATEST_VERSION}'
}
AccessKey
All Android demos require a valid Picovoice AccessKey at initialization. AccessKeys act as your credentials when using Porcupine SDKs.
You can create your AccessKey for free. Make sure to keep your AccessKey secret.
To obtain your AccessKey:
- Login or Signup for a free account on the Picovoice Console.
- Once logged in, go to the
AccessKeytab to create one or use an existingAccessKey.
Permissions
To enable AccessKey validation and recording with your Android device's microphone, you must add the following line to your AndroidManifest.xml file:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
Usage
The module provides you with two levels of API to choose from depending on your needs.
High-Level API
PorcupineManager provides a high-level API that takes care of audio recording and wake word detection. This class is the quickest way to get started.
To create an instance of PorcupineManager, use the PorcupineManager Builder:
import ai.picovoice.porcupine.*;
final String accessKey = "${ACCESS_KEY}";
try {
Porcupine.BuiltInKeyword[] keywords = new Porcupine.BuiltInKeyword[]{
Porcupine.BuiltInKeyword.PICOVOICE,
Porcupine.BuiltInKeyword.PORCUPINE
}
PorcupineManager porcupineManager = new PorcupineManager.Builder()
.setAccessKey(accessKey)
.setKeywords(keywords)
.build(context, wakeWordCallback);
} catch (PorcupineException e) { }
The context parameter is the Android application context - this is used to extract Porcupine resources from the APK. The wakeWordCallback parameter is PorcupineManagerCallback that will be invoked when Porcupine has detected one of the keywords.
The callback should accept a single integer, keywordIndex, which specifies which wake word has been detected.
PorcupineManagerCallback wakeWordCallback = new PorcupineManagerCallback() {
@Override
public void invoke(int keywordIndex) {
if(keywordIndex == 0){
// picovoice detected!
}
else if(keywordIndex == 1){
// porcupine detected!
}
}
}
Available built-in keywords are accessible via the Porcupine.BuiltInKeyword enum.
To create an instance of PorcupineManager that detects custom keywords, you can use the setKeywordPaths() builder argument instead:
final String accessKey = "${ACCESS_KEY}";
try {
String[] keywordPaths = new String[]{
"absolute/path/to/keyword/one.ppn",
"absolute/path/to/keyword/two.ppn"
}
PorcupineManager porcupineManager = new PorcupineManager.Builder()
.setAccessKey(accessKey)
.setKeywordPaths(keywordPaths)
.build(context, wakeWordCallback);
} catch (PorcupineException e) { }
In addition to custom keywords, you can override the default Porcupine english model file and/or keyword sensitivities.
Sensitivity is the parameter that enables trading miss rate for the false alarm rate. It is a floating-point number within [0, 1]. A higher sensitivity reduces the miss rate at the cost of increased false alarm rate.
The model file contains the parameters for the wake word engine. To change the language that Porcupine understands, you'll pass in a different model file.
There is also the option to pass an error callback, which will be invoked if an error is encountered while PorcupineManager is processing audio.
These optional parameters can be set like so:
final String accessKey = "${ACCESS_KEY}";
try {
PorcupineManager porcupineManager = new PorcupineManager.Builder()
.setAccessKey(accessKey)
.setKeywordPaths(keywordPaths)
.setModelPath("absolute/path/to/porcupine_model.pv")
.setSensitivities(new float[] { 0.6f, 0.35f })
.setErrorCallback(new PorcupineManagerErrorCallback() {
@Override
public void invoke(PorcupineExcpetion e) {
// process error
}
})
.build(context, wakeWordCallback);
} catch (PorcupineException e) { }
Once you have instantiated a PorcupineManager, you can start audio capture and wake word detection by calling:
porcupineManager.start();
And then stop it by calling:
try{
porcupineManager.start();
} catch (PorcupineException e) { }
Once the app is done with using an instance of PorcupineManager, be sure you explicitly release the resources allocated to Porcupine:
porcupineManager.delete();
Low-Level API
Porcupine provides low-level access to the wake word engine for those who want to incorporate wake word detection into a already existing audio processing pipeline.
Porcupine uses a Builder pattern to construct instances.
import ai.picovoice.porcupine.*;
final String accessKey = "${ACCESS_KEY}";
try {
Porcupine porcupine = new Porcupine.Builder()
.setAccessKey(accessKey)
.setKeyword(Porcupine.BuiltInKeyword.PICOVOICE)
.build(context);
} catch (PorcupineException e) { }
To search for a keyword in audio, you must pass frames of audio to Porcupine using the process function. The keywordIndex returned will either be -1 if no detection was made or an integer specifying which keyword was detected.
short[] getNextAudioFrame(){
// .. get audioFrame
return audioFrame;
}
while(true) {
try {
int keywordIndex = porcupine.process(getNextAudioFrame());
if(keywordIndex >= 0) {
// .. detection made!
}
} catch (PorcupineException e) { }
}
For process to work correctly, the audio data must be in the audio format required by Picovoice.
The required audio format is found by calling .getSampleRate() to get the required sample rate and .getFrameLength() to get the required number of samples per input frame. Audio must be single-channel and 16-bit linearly-encoded.
Once you're done with Porcupine, ensure you release its resources explicitly:
porcupine.delete();
Custom Wake Word Integration
To add a custom wake word or model file to your application, add the files to your assets folder (src/main/assets) and then pass the path to the Porcupine Builder:
// in this example our files are located at '/assets/picovoice_files/keyword.ppn' and '/assets/picovoice_files/model.pv'
final String accessKey = "${ACCESS_KEY}";
try {
Porcupine porcupine = new Porcupine.Builder()
.setAccessKey(accessKey)
.setKeywordPath("picovoice_files/keyword.ppn")
.setModelPath("picovoice_files/model.pv")
.build(context);
} catch (PorcupineException e) { }
Non-English Wake Words
In order to detect non-English wake words you need to use the corresponding model file. The model files for all supported languages are available here.
Demo Apps
For example usage refer to the Activity demo, Service demo or STT demo.
Resource Usage
The following profile graph was captured running the Porcupine Activity demo on a Google Pixel 3:
- CPU <= 1%
- Battery Usage <= LOW
- Memory <= 128 MB
