Porcupine Binding for Flutter

Porcupine

Porcupine 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 2.8.1+ on the following platforms:

  • Android 5.0+ (API 21+)
  • iOS 13.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_flutter: ^<version>

If you prefer to clone the repo and use it locally you can reference the local binding location:

dependencies:
  porcupine_flutter:
    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'.

AccessKey

Porcupine requires a valid Picovoice AccessKey at initialization. AccessKey acts as your credentials when using Porcupine SDKs. You can get your AccessKey for free. Make sure to keep your AccessKey secret. Signup or Login to Picovoice Console to get your AccessKey.

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

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

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

final String accessKey = "{ACCESS_KEY}";  // AccessKey obtained from Picovoice Console (https://console.picovoice.ai/)

void createPorcupineManager() async {
    try{
        _porcupineManager = await PorcupineManager.fromBuiltInKeywords(
            accessKey,
            [BuiltInKeyword.PICOVOICE, BuiltInKeyword.PORCUPINE],
            _wakeWordCallback);
    } on PorcupineException 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 BuiltInKeyword enum.

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).

final String accessKey = "{ACCESS_KEY}";  // AccessKey obtained from Picovoice Console (https://console.picovoice.ai/)

_porcupineManager = await PorcupineManager.fromKeywordPaths(
    accessKey,
    ["/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:

final String accessKey = "{ACCESS_KEY}";  // AccessKey obtained from Picovoice Console (https://console.picovoice.ai/)

_porcupineManager = await PorcupineManager.fromKeywordPaths(
    accessKey,
    ["/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(PorcupineException 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 PorcupineException 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 overridden 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 an already existing audio processing pipeline.

Porcupine also has fromBuiltInKeywords and fromKeywordPaths static constructors.

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

final String accessKey = "{ACCESS_KEY}";  // AccessKey obtained from Picovoice Console (https://console.picovoice.ai/)

void createPorcupine() async {
    try{
        _porcupine = await Porcupine.fromBuiltInKeywords(
            accessKey,
            [BuiltInKeyword.PICOVOICE]);
    } on PorcupineException 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 PorcupineException 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:

final String accessKey = "{ACCESS_KEY}";  // AccessKey obtained from Picovoice Console (https://console.picovoice.ai/)

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

Alternatively, if the model files are deployed to the device with a different method, the absolute paths to the files on device can be used.

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!