Skip to content

Commit

Permalink
Merge pull request #2722 from forslund/bugfix/latest-porcupine
Browse files Browse the repository at this point in the history
Update for newer Porcupine engines
  • Loading branch information
krisgesling committed Jan 25, 2021
2 parents 8020e54 + 4b1efc7 commit 73f0299
Showing 1 changed file with 45 additions and 32 deletions.
77 changes: 45 additions & 32 deletions mycroft/client/speech/hotword_factory.py
Expand Up @@ -14,29 +14,27 @@
#
"""Factory functions for loading hotword engines - both internal and plugins.
"""
from time import time, sleep
from contextlib import suppress
from glob import glob
import os
from os.path import dirname, exists, join, abspath, expanduser, isfile, isdir
import platform
import posixpath
from shutil import rmtree
import struct
import sys
import tempfile
import requests
from contextlib import suppress
from glob import glob
from os.path import dirname, exists, join, abspath, expanduser, isfile, isdir
from shutil import rmtree
from threading import Timer, Thread
from time import time, sleep
from urllib.error import HTTPError

from petact import install_package
import requests

from mycroft.configuration import Configuration, LocalConf, USER_CONFIG
from mycroft.util.log import LOG
from mycroft.util.monotonic_event import MonotonicEvent
from mycroft.util.log import LOG
from mycroft.util.plugins import load_plugin


RECOGNIZER_DIR = join(abspath(dirname(__file__)), "recognizer")
INIT_TIMEOUT = 10 # In seconds

Expand Down Expand Up @@ -357,29 +355,27 @@ def found_wake_word(self, frame_data):


class PorcupineHotWord(HotWordEngine):
"""Hotword engine using picovoice's Porcupine hot word engine.
TODO: Remove in 21.02
"""
def __init__(self, key_phrase="hey mycroft", config=None, lang="en-us"):
super(PorcupineHotWord, self).__init__(key_phrase, config, lang)
porcupine_path = expanduser(self.config.get(
"porcupine_path", join('~', '.mycroft', 'Porcupine')))
super().__init__(key_phrase, config, lang)
keyword_file_paths = [expanduser(x.strip()) for x in self.config.get(
"keyword_file_path", "hey_mycroft.ppn").split(',')]
sensitivities = self.config.get("sensitivities", 0.5)
bindings_path = join(porcupine_path, 'binding/python')
LOG.info('Adding %s to Python path' % bindings_path)
sys.path.append(bindings_path)

try:
from porcupine import Porcupine
except ImportError:
from pvporcupine.porcupine import Porcupine
from pvporcupine.util import (pv_library_path,
pv_model_path)
except ImportError as err:
raise Exception(
"Python bindings for Porcupine not found. "
"Please use --porcupine-path to set Porcupine base path")

system = platform.system()
machine = platform.machine()
library_path = join(
porcupine_path, 'lib/linux/%s/libpv_porcupine.so' % machine)
model_file_path = join(
porcupine_path, 'lib/common/porcupine_params.pv')
"Please run \"mycroft-pip install pvporcupine\"") from err

library_path = pv_library_path('')
model_file_path = pv_model_path('')
if isinstance(sensitivities, float):
sensitivities = [sensitivities] * len(keyword_file_paths)
else:
Expand All @@ -388,41 +384,58 @@ def __init__(self, key_phrase="hey mycroft", config=None, lang="en-us"):
self.audio_buffer = []
self.has_found = False
self.num_keywords = len(keyword_file_paths)

LOG.warning('The Porcupine wakeword engine shipped with '
'Mycroft-core is deprecated and will be removed in '
'mycroft-core 21.02. Use the mycroft-porcupine-plugin '
'instead.')
LOG.info(
'Loading Porcupine using library path {} and keyword paths {}'
.format(library_path, keyword_file_paths))
self.porcupine = Porcupine(
library_path=library_path,
model_file_path=model_file_path,
keyword_file_paths=keyword_file_paths,
model_path=model_file_path,
keyword_paths=keyword_file_paths,
sensitivities=sensitivities)

LOG.info('Loaded Porcupine')

def update(self, chunk):
"""Update detection state from a chunk of audio data.
Arguments:
chunk (bytes): Audio data to parse
"""
pcm = struct.unpack_from("h" * (len(chunk)//2), chunk)
self.audio_buffer += pcm
while True:
if len(self.audio_buffer) >= self.porcupine.frame_length:
result = self.porcupine.process(
self.audio_buffer[0:self.porcupine.frame_length])
# result could be boolean (if there is one keword)
# or int (if more than one keyword)
self.has_found |= (
(self.num_keywords == 1 and result) |
(self.num_keywords > 1 and result >= 0))
# result will be the index of the found keyword or -1 if
# nothing has been found.
self.has_found |= result >= 0
self.audio_buffer = self.audio_buffer[
self.porcupine.frame_length:]
else:
return

def found_wake_word(self, frame_data):
"""Check if wakeword has been found.
Returns:
(bool) True if wakeword was found otherwise False.
"""
if self.has_found:
self.has_found = False
return True
return False

def stop(self):
"""Stop the hotword engine.
Clean up Porcupine library.
"""
if self.porcupine is not None:
self.porcupine.delete()

Expand Down

0 comments on commit 73f0299

Please sign in to comment.