From 8dc231358408b40972dea2944c86c866a92504ba Mon Sep 17 00:00:00 2001 From: MWorkman99 <54334201+MWorkman99@users.noreply.github.com> Date: Thu, 27 Feb 2020 12:59:29 -0500 Subject: [PATCH 1/2] Add new_message_callback support. --- examples/Python/snowboydecoder_arecord.py | 132 ++++++++++++++++++---- 1 file changed, 110 insertions(+), 22 deletions(-) diff --git a/examples/Python/snowboydecoder_arecord.py b/examples/Python/snowboydecoder_arecord.py index efa3be46..2353fbc9 100644 --- a/examples/Python/snowboydecoder_arecord.py +++ b/examples/Python/snowboydecoder_arecord.py @@ -8,10 +8,15 @@ import logging import subprocess import threading +import sys logging.basicConfig() logger = logging.getLogger("snowboy") logger.setLevel(logging.INFO) + + +logging.getLogger().addHandler(logging.StreamHandler(sys.stdout)) + TOP_DIR = os.path.dirname(os.path.abspath(__file__)) RESOURCE_FILE = os.path.join(TOP_DIR, "resources/common.res") @@ -34,6 +39,9 @@ def get(self): self._buf.clear() return tmp + def clear(self): + self._buf.clear() + def play_audio_file(fname=DETECT_DING): """Simple callback function to play a wave file. By default it plays @@ -44,7 +52,6 @@ def play_audio_file(fname=DETECT_DING): """ os.system("aplay " + fname + " > /dev/null 2>&1") - class HotwordDetector(object): """ Snowboy decoder to detect whether a keyword specified by `decoder_model` @@ -92,15 +99,23 @@ def __init__(self, decoder_model, def record_proc(self): CHUNK = 2048 RECORD_RATE = 16000 - cmd = 'arecord -q -r %d -f S16_LE' % RECORD_RATE - process = subprocess.Popen(cmd.split(' '), - stdout = subprocess.PIPE, - stderr = subprocess.PIPE) - wav = wave.open(process.stdout, 'rb') + + # per https://github.com/evancohen/sonus/pull/93/commits + DEADMAX = 20 + while self.recording: - data = wav.readframes(CHUNK) - self.ring_buffer.extend(data) - process.terminate() + dead_counter = 0 + cmd = 'arecord --device=plughw:1,0 -q -r %d -f S16_LE' % RECORD_RATE + process = subprocess.Popen(cmd.split(' '), + stdout = subprocess.PIPE, + stderr = subprocess.PIPE) + wav = wave.open(process.stdout, 'rb') + while self.recording and dead_counter < DEADMAX: + data = wav.readframes(CHUNK) + if len(data) < 100: + dead_counter += 1 + self.ring_buffer.extend(data) + process.terminate() def init_recording(self): """ @@ -112,7 +127,10 @@ def init_recording(self): def start(self, detected_callback=play_audio_file, interrupt_check=lambda: False, - sleep_time=0.03): + sleep_time=0.03, + audio_recorder_callback=None, + silent_count_threshold=3, + recording_timeout=100): """ Start the voice detector. For every `sleep_time` second it checks the audio buffer for triggering keywords. If detected, then call @@ -125,8 +143,18 @@ def start(self, detected_callback=play_audio_file, items must match the number of models in `decoder_model`. :param interrupt_check: a function that returns True if the main loop - needs to stop. + needs to stop. :param float sleep_time: how much time in second every loop waits. + :param audio_recorder_callback: if specified, this will be called after + a keyword has been spoken and after the + phrase immediately after the keyword has + been recorded. The function will be + passed the name of the file where the + phrase was recorded. + :param silent_count_threshold: indicates how long silence must be heard + to mark the end of a phrase that is + being recorded. + :param recording_time out: limits the maximum length of a recording. :return: None """ @@ -147,8 +175,12 @@ def start(self, detected_callback=play_audio_file, "callbacks (%d)" % (self.num_hotwords, len(detected_callback)) logger.debug("detecting...") + + state = "PASSIVE" while True: + + if interrupt_check(): logger.debug("detect voice break") break @@ -157,20 +189,77 @@ def start(self, detected_callback=play_audio_file, time.sleep(sleep_time) continue - ans = self.detector.RunDetection(data) - if ans == -1: + status = self.detector.RunDetection(data) + if status == -1: logger.warning("Error initializing streams or reading audio data") - elif ans > 0: - message = "Keyword " + str(ans) + " detected at time: " - message += time.strftime("%Y-%m-%d %H:%M:%S", - time.localtime(time.time())) - logger.info(message) - callback = detected_callback[ans-1] - if callback is not None: - callback() + + if state == "PASSIVE": + print (state) + + if status > 0: #key word found + self.recordedData = [] + self.recordedData.append(data) + silentCount = 0 + recordingCount = 0 + message = "Keyword " + str(status) + " detected at time: " + message += time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(time.time())) + logger.info(message) + callback = detected_callback[status-1] + if callback is not None: + callback() + + if audio_recorder_callback is not None: + state = "ACTIVE" + self.ring_buffer.clear() + continue + + elif state == "ACTIVE": + print (silentCount) + stopRecording = False + + # hard limit + if recordingCount > recording_timeout: + stopRecording = True + + elif status == -2: #silence found + if silentCount > silent_count_threshold: + stopRecording = True + else: + silentCount = silentCount + 1 + elif status == 0: #voice found + silentCount = 0 + + if stopRecording == True: + fname = self.saveMessage(data) + audio_recorder_callback(fname) + state = "PASSIVE" + continue + + recordingCount = recordingCount + 1 + self.recordedData.append(data) logger.debug("finished.") + def saveMessage(self,data): + logger.info("starting saveMessage") + """ + Save the message stored in self.recordedData to a timestamped file. + """ + filename = 'output' + str(int(time.time())) + '.wav' + data = b''.join(self.recordedData) + + #use wave to save data + wf = wave.open(filename, 'wb') + wf.setnchannels(1) + wf.setsampwidth(2) # based on arecord settings, from above. + print (self.detector.SampleRate()) + wf.setframerate(self.detector.SampleRate()) + wf.writeframes(data) + wf.close() + logger.debug("finished saving: " + filename) + return filename + def terminate(self): """ Terminate audio stream. Users cannot call start() again to detect. @@ -178,4 +267,3 @@ def terminate(self): """ self.recording = False self.record_thread.join() - From 2512ad83c5fa3de5eaa3135e380d000f1294411c Mon Sep 17 00:00:00 2001 From: MWorkman99 <54334201+MWorkman99@users.noreply.github.com> Date: Thu, 27 Feb 2020 14:02:29 -0500 Subject: [PATCH 2/2] Create demo using arecord with audio_recorder_callback --- examples/Python/demo_arecord2.py | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 examples/Python/demo_arecord2.py diff --git a/examples/Python/demo_arecord2.py b/examples/Python/demo_arecord2.py new file mode 100644 index 00000000..52d3c631 --- /dev/null +++ b/examples/Python/demo_arecord2.py @@ -0,0 +1,48 @@ +import snowboydecoder_arecord +import sys +import signal + +""" +This demo file shows you how to use the new_message_callback to interact with +the recorded audio after a keyword is spoken. It saves the recorded audio to a +wav file. +""" + + +interrupted = False + + +def signal_handler(signal, frame): + global interrupted + interrupted = True + +def audioRecorderCallback(fname): + print ("call google to STT: " + fname) + +def interrupt_callback(): + global interrupted + return interrupted + +if len(sys.argv) == 1: + print("Error: need to specify model name") + print("Usage: python demo.py your.model") + sys.exit(-1) + +model = sys.argv[1] + +# capture SIGINT signal, e.g., Ctrl+C +signal.signal(signal.SIGINT, signal_handler) + +detector = snowboydecoder_arecord.HotwordDetector(model, sensitivity=0.5) +print('Listening... Press Ctrl+C to exit') + +def audioRecorderCallback(fname): + print ('file is complete ' + fname) + +# main loop +detector.start( detected_callback=None, #snowboydecoder_arecord.play_audio_file, + interrupt_check=interrupt_callback, + audio_recorder_callback=audioRecorderCallback, + sleep_time=0.01) + +detector.terminate()