1
0
mirror of https://github.com/Picovoice/porcupine.git synced 2022-01-28 03:27:53 +03:00
Files
2021-11-25 12:33:09 -08:00

213 lines
7.3 KiB
Dart

//
// Copyright 2020-2021 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.
//
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:path_provider/path_provider.dart';
import 'package:porcupine_flutter/porcupine_error.dart';
/// BuiltInKeywords
enum BuiltInKeyword {
ALEXA,
AMERICANO,
BLUEBERRY,
BUMBLEBEE,
COMPUTER,
GRAPEFRUIT,
GRASSHOPPER,
HEY_GOOGLE,
HEY_SIRI,
JARVIS,
OK_GOOGLE,
PICOVOICE,
PORCUPINE,
TERMINATOR
}
class Porcupine {
static final MethodChannel _channel = MethodChannel("porcupine");
String? _handle;
final int _frameLength;
final int _sampleRate;
final String _version;
/// Porcupine version string
String get version => _version;
/// The number of audio samples per frame required by Porcupine
int get frameLength => _frameLength;
/// The audio sample rate required by Porcupine
int get sampleRate => _sampleRate;
/// Static creator for initializing Porcupine from a selection of built-in keywords
///
/// [accessKey] AccessKey obtained from Picovoice Console (https://console.picovoice.ai/).
///
/// [keywords] is a List of (phrases) for detection. The list of available
/// keywords can be retrieved using [BuiltInKeyword] enum.
///
/// [modelPath] is a path to the file containing model parameters. If not set
/// it will be set to the default location.
///
/// [sensitivities] sensitivities for each keywords model. A higher sensitivity
/// reduces miss rate at the cost of potentially higher false alarm rate.
/// Sensitivity should be a floating-point number within 0 and 1.
///
/// Thows a `PorcupineException` if not initialized correctly
///
/// returns an instance of the wake word engine
static Future<Porcupine> fromBuiltInKeywords(
String accessKey, List<BuiltInKeyword> keywords,
{String? modelPath, List<double>? sensitivities}) async {
if (modelPath != null) {
modelPath = await _tryExtractFlutterAsset(modelPath);
}
List<String> keywordValues = List.empty(growable: true);
for (var keyword in keywords) {
keywordValues.add(keyword
.toString()
.split('.')
.last
.replaceAll('_', ' ')
.toLowerCase());
}
try {
Map<String, dynamic> result = Map<String, dynamic>.from(
await _channel.invokeMethod('from_builtin_keywords', {
'accessKey': accessKey,
'modelPath': modelPath,
'keywords': keywordValues,
'sensitivities': sensitivities
}));
return Porcupine._(result['handle'], result['frameLength'],
result['sampleRate'], result['version']);
} on PlatformException catch (error) {
throw porcupineStatusToException(error.code, error.message);
} on Exception catch (error) {
throw porcupineStatusToException("PorcupineException", error.toString());
}
}
/// Static creator for initializing Porcupine from a list of paths to custom keyword files
///
/// [accessKey] AccessKey obtained from Picovoice Console (https://console.picovoice.ai/).
///
/// [keywordPaths] A List of absolute paths to keyword model files.
///
/// [modelPath] is a path to the file containing model parameters. If not set
/// it will be set to the default location.
///
/// [sensitivities] sensitivities for each keywords model. A higher sensitivity
/// reduces miss rate at the cost of potentially higher false alarm rate.
/// Sensitivity should be a floating-point number within 0 and 1.
///
/// Thows a `PorcupineException` if not initialized correctly
///
/// returns an instance of the wake word engine
static Future<Porcupine> fromKeywordPaths(
String accessKey, List<String> keywordPaths,
{String? modelPath, List<double>? sensitivities}) async {
if (modelPath != null) {
modelPath = await _tryExtractFlutterAsset(modelPath);
}
for (var i = 0; i < keywordPaths.length; i++) {
keywordPaths[i] = await _tryExtractFlutterAsset(keywordPaths[i]);
}
try {
Map<String, dynamic> result = Map<String, dynamic>.from(
await _channel.invokeMethod('from_keyword_paths', {
'accessKey': accessKey,
'modelPath': modelPath,
'keywordPaths': keywordPaths,
'sensitivities': sensitivities
}));
return Porcupine._(result['handle'], result['frameLength'],
result['sampleRate'], result['version']);
} on PlatformException catch (error) {
throw porcupineStatusToException(error.code, error.message);
} on Exception catch (error) {
throw porcupineStatusToException("PorcupineException", error.toString());
}
}
// private constructor
Porcupine._(this._handle, this._frameLength, this._sampleRate, this._version);
/// Process a frame of audio with the wake word engine.
///
/// [frame] is a frame of frame of audio samples to be assessed by Porcupine. 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.
///
/// Index of the detected keyword, or -1 if no detection occurred
Future<int> process(List<int>? frame) async {
try {
int keywordIndex = await _channel
.invokeMethod('process', {'handle': _handle, 'frame': frame});
return keywordIndex;
} on PlatformException catch (error) {
throw porcupineStatusToException(error.code, error.message);
} on Exception catch (error) {
throw porcupineStatusToException("PorcupineException", error.toString());
}
}
/// Frees memory that was allocated for Porcupine
Future<void> delete() async {
if (_handle != null) {
await _channel.invokeMethod('delete', {'handle': _handle});
_handle = null;
}
}
static Future<String> _tryExtractFlutterAsset(String filePath) async {
ByteData data;
try {
data = await rootBundle.load(filePath);
} catch (_) {
// In flutter, a resource can be added through flutter's assets directory
// or natively (res for android; bundle for iOS). We try to extract
// a resource in flutter's assets directory and if it fails, try to load
// the resource using native modules.
return filePath;
}
try {
String resourceDirectory =
(await getApplicationDocumentsDirectory()).path;
String outputPath = '$resourceDirectory/$filePath';
File outputFile = File(outputPath);
final buffer = data.buffer;
await outputFile.create(recursive: true);
await outputFile.writeAsBytes(
buffer.asUint8List(data.offsetInBytes, data.lengthInBytes));
return outputPath;
} catch (_) {
throw porcupineStatusToException(
"PorcupineIOException", "failed to extract '$filePath'");
}
}
}