Porcupine Binding for Flutter

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.

Compatibility

This binding is for running Porcupine on Flutter 1.20.0+ on the following platforms:

  • Android 4.1+ (API 16+)
  • iOS 9.0+

Installation

To start, you must have the Flutter SDK installed on your system. Once installed, you can run flutter doctor to determine any other missing requirements.

To add the Porcupine plugin to your app project, you can reference it in your pub.yaml:

dependencies:  
  porcupine: ^<version>

If you prefer to clone the repo and use it locally, first run copy_resources.sh (NOTE: on Windows, Git Bash or another bash shell is required, or you will have to manually copy the libs into the project.). Then you can reference the local binding location:

dependencies:  
  porcupine:
    path: /path/to/porcupine/flutter/binding

NOTE: When archiving for release on iOS, you may have to change the build settings of your project in order to prevent stripping of the Porcupine library. To do this open the Runner project in XCode and change build setting Deployment -> Strip Style to 'Non-Global Symbols'.

Permissions

To enable recording with the hardware's microphone, you must first ensure that you have enabled the proper permission on both iOS and Android.

On iOS, open your Info.plist and add the following line:

<key>NSMicrophoneUsageDescription</key>
<string>[Permission explanation]</string>

On Android, open your AndroidManifest.xml and add the following line:

<uses-permission android:name="android.permission.RECORD_AUDIO" />

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. This class is the quickest way to get started.

Using the constructor PorcupineManager.fromKeywords will create an instance of the PorcupineManager using one or more of the built-in keywords.

import 'package:porcupine/porcupine_manager.dart';
import 'package:porcupine/porcupine_error.dart';

void createPorcupineManager() async {
    try{
        _porcupineManager = await PorcupineManager.fromKeywords(
            ["picovoice", "porcupine"],
            _wakeWordCallback);
    } on PvError catch (err) {
        // handle porcupine init error
    }
}

NOTE: the call is asynchronous and therefore should be called in an async block with a try/catch.

The wakeWordCallback parameter is a function that you want to execute when Porcupine has detected one of the keywords. The function should accept a single integer, keywordIndex, which specifies which wake word has been detected.

void _wakeWordCallback(int keywordIndex){
    if(keywordIndex == 0){
        // picovoice detected
    }
    else if (keywordIndex === 1){
        // porcupine detected
    }
}

Available built-in keywords are stored in the constants PorcupineManager.BUILT_IN_KEYWORDS and Porcupine.BUILT_IN_KEYWORDS.

To create an instance of PorcupineManager that detects custom keywords, you can use the PorcupineManager.fromKeywordPaths static constructor and provide the paths to the .ppn file(s).

_porcupineManager = await PorcupineManager.fromKeywordPaths(
    ["/path/to/keyword.ppn"], 
    _wakeWordCallback);

In addition to custom keywords, you can override the default Porcupine model file and/or keyword sensitivities, as well as add an error callback you want to trigger if there's a problem encountered while Porcupine is processing frames.

These optional parameters can be passed in like so:

_porcupineManager = await PorcupineManager.fromKeywordPaths(
    ["/path/to/keyword/file/one.ppn", "/path/to/keyword/file/two.ppn"],
    _wakeWordCallback,
    modelPath: 'path/to/model/file.pv',
    sensitivities: [0.25, 0.6],
    errorCallback: _errorCallback);

void _errorCallback(PvError error){
    print(error.message);
}

Once you have instantiated a PorcupineManager, you can start audio capture and wake word detection by calling:

try{
    await _porcupineManager.start();
} on PvAudioException catch (ex) {
    // deal with either audio exception     
}

And then stop it by calling:

await _porcupineManager.stop();

Once the app is done with using an instance of PorcupineManager, be sure you explicitly release the resources allocated to Porcupine:

await _porcupineManager.delete();

NOTE: Avoid calling delete() from the paused state unless you have overidden the back button functionality on Android with WillPopScope.

There is no need to deal with audio capture to enable wake word detection with PorcupineManager. This is because it uses our flutter_voice_processor Flutter plugin to capture frames of audio and automatically pass it to the wake word engine.

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 also has fromKeywords and fromKeywordPaths static constructors.

import 'package:porcupine/porcupine_manager.dart';
import 'package:porcupine/porcupine_error.dart';

void createPorcupine() async {
    try{
        _porcupine = await Porcupine.fromKeywords(["picovoice"]);
    } on PvError catch (err) {
        // handle porcupine init error
    }
}

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.

List<int> buffer = getAudioFrame();

try {
    int keywordIndex = _porcupine.process(buffer);
    if (keywordIndex >= 0) {
        // detection made!
    }
} on PvError catch (error) {
    // handle error
}

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 .sampleRate to get the required sample rate and .frameLength to get the required frame size. Audio must be single-channel and 16-bit linearly-encoded.

Finally, once you no longer need the wake word engine, be sure to explicitly release the resources allocated to Porcupine:

_porcupine.delete();

Custom Wake Word Integration

To add a custom wake word to your Flutter application, first add it to an assets folder in your project directory. Then add them to you your pubspec.yaml:

flutter:
  assets:
    - assets/keyword.ppn

You can then pass it directly to Porcupine's fromKeywordPaths constructor:

String keywordAsset = "assets/keyword.ppn"
try{
    _porcupine = await Porcupine.fromKeywordPaths([keywordAsset]);
} on PvError catch (err) {
    // handle porcupine init error
}

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 App

Check out the Porcupine Flutter demo to see what it looks like to use Porcupine in a cross-platform app!