Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade arecord samples to support audio_recorder_callback #644

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions examples/Python/demo_arecord2.py
Original file line number Diff line number Diff line change
@@ -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()
132 changes: 110 additions & 22 deletions examples/Python/snowboydecoder_arecord.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand All @@ -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`
Expand Down Expand Up @@ -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):
"""
Expand All @@ -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
Expand All @@ -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
"""

Expand All @@ -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
Expand All @@ -157,25 +189,81 @@ 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.
:return: None
"""
self.recording = False
self.record_thread.join()