Logging to cmd now a client param, player uses a thread, recording formatting and prerol of mic (in client)
This commit is contained in:
parent
fa502203a1
commit
7944717e8e
5 changed files with 74 additions and 10 deletions
|
@ -12,6 +12,8 @@ from zmq.asyncio import Context
|
||||||
import sys
|
import sys
|
||||||
from hugvey.communication import LOG_BS
|
from hugvey.communication import LOG_BS
|
||||||
import os
|
import os
|
||||||
|
import collections
|
||||||
|
import math
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import alsaaudio
|
import alsaaudio
|
||||||
|
@ -24,6 +26,8 @@ import subprocess
|
||||||
|
|
||||||
logger = logging.getLogger("client")
|
logger = logging.getLogger("client")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def setLogger(hv_id):
|
def setLogger(hv_id):
|
||||||
global logger
|
global logger
|
||||||
logger = logging.getLogger("hugvey").getChild("{}".format(hv_id)).getChild("client")
|
logger = logging.getLogger("hugvey").getChild("{}".format(hv_id)).getChild("client")
|
||||||
|
@ -44,6 +48,11 @@ class VoiceServer(object):
|
||||||
self.loop = loop
|
self.loop = loop
|
||||||
self.hugvey = hugvey
|
self.hugvey = hugvey
|
||||||
|
|
||||||
|
self.chunk = 4096
|
||||||
|
self.mic_prerol_sec = .3
|
||||||
|
self.prerol_frame_count = math.ceil(self.input_rate / self.chunk)
|
||||||
|
self.prerol_frames = collections.deque(maxlen = self.prerol_frame_count)
|
||||||
|
|
||||||
def get_input_idx(self):
|
def get_input_idx(self):
|
||||||
input_device_idx = None
|
input_device_idx = None
|
||||||
# input_device_idx = 6
|
# input_device_idx = 6
|
||||||
|
@ -74,8 +83,19 @@ class VoiceServer(object):
|
||||||
if self.hugvey.cmd_server.playPopen is not None:
|
if self.hugvey.cmd_server.playPopen is not None:
|
||||||
logger.debug('block recording {}' .format(
|
logger.debug('block recording {}' .format(
|
||||||
self.hugvey.cmd_server.playPopen))
|
self.hugvey.cmd_server.playPopen))
|
||||||
|
|
||||||
|
# if recording is blocked, store the latest n frames. So we can release
|
||||||
|
# this buffer to the mic stream
|
||||||
|
self.prerol_frames.append(f)
|
||||||
|
|
||||||
# multiply by 0 to disable audio recording while playback
|
# multiply by 0 to disable audio recording while playback
|
||||||
f = audioop.mul(f, 2, 0)
|
f = audioop.mul(f, 2, 0)
|
||||||
|
elif len(self.prerol_frames) > 0:
|
||||||
|
self.prerol_frames.append(f)
|
||||||
|
f = b''.join(self.prerol_frames)
|
||||||
|
self.prerol_frames.clear()
|
||||||
|
logger.info('used buffer, len now {}'.format(len(f)))
|
||||||
|
|
||||||
|
|
||||||
self.loop.call_soon_threadsafe(self.voice_socket.send, f)
|
self.loop.call_soon_threadsafe(self.voice_socket.send, f)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -92,7 +112,9 @@ class VoiceServer(object):
|
||||||
self.stopped = False
|
self.stopped = False
|
||||||
|
|
||||||
# wait a sec for the input devices to come up
|
# wait a sec for the input devices to come up
|
||||||
await asyncio.sleep(2)
|
logger.debug('wait for mic')
|
||||||
|
await asyncio.sleep(3)
|
||||||
|
logger.debug('done waiting for mic')
|
||||||
|
|
||||||
stream = self.p.open(
|
stream = self.p.open(
|
||||||
format=FORMAT,
|
format=FORMAT,
|
||||||
|
@ -288,7 +310,6 @@ class Hugvey(object):
|
||||||
def __init__(self, id = None):
|
def __init__(self, id = None):
|
||||||
self.id = self.getId() if id is None else id
|
self.id = self.getId() if id is None else id
|
||||||
setLogger(self.id)
|
setLogger(self.id)
|
||||||
logger.debug('Hugvey {}, reporting'.format(self.id))
|
|
||||||
|
|
||||||
def getId(self) -> int:
|
def getId(self) -> int:
|
||||||
"""Get Hugvey ID from hostname"""
|
"""Get Hugvey ID from hostname"""
|
||||||
|
@ -310,6 +331,8 @@ class Hugvey(object):
|
||||||
return await self.cmd_server.command_listener()
|
return await self.cmd_server.command_listener()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
logger.debug('Hugvey {}, reporting'.format(self.id))
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
if self.config['voice']['play_device'] and 'alsaaudio' in sys.modules:
|
if self.config['voice']['play_device'] and 'alsaaudio' in sys.modules:
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import pyaudio
|
import pyaudio
|
||||||
import logging
|
import logging
|
||||||
import audioop
|
import audioop
|
||||||
|
import threading
|
||||||
|
import queue
|
||||||
|
from hugvey.communication import LOG_BS
|
||||||
|
|
||||||
mainLogger = logging.getLogger("hugvey")
|
mainLogger = logging.getLogger("hugvey")
|
||||||
logger = mainLogger.getChild("player")
|
logger = mainLogger.getChild("player")
|
||||||
|
@ -31,6 +34,12 @@ class Player:
|
||||||
self.p.terminate()
|
self.p.terminate()
|
||||||
self.p = None
|
self.p = None
|
||||||
|
|
||||||
|
self.thread = threading.Thread(target=self.play, name="player")
|
||||||
|
self.is_playing = threading.Event()
|
||||||
|
self.is_playing.set()
|
||||||
|
self.play_q = queue.Queue(maxsize=100)
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
def get_output_idx(self):
|
def get_output_idx(self):
|
||||||
output_device_idx = None
|
output_device_idx = None
|
||||||
output_device_idx = 14
|
output_device_idx = 14
|
||||||
|
@ -44,6 +53,21 @@ class Player:
|
||||||
logger.debug("{} {:0d} {}".format("* " if output_device_idx == i else "- ", i, dev['name']))
|
logger.debug("{} {:0d} {}".format("* " if output_device_idx == i else "- ", i, dev['name']))
|
||||||
return output_device_idx
|
return output_device_idx
|
||||||
|
|
||||||
|
def play(self):
|
||||||
|
"""
|
||||||
|
Because the stream.write is a blocking action it delays the main thread significantly
|
||||||
|
Therefore we put it in its own thread
|
||||||
|
"""
|
||||||
|
logger.info("Start player")
|
||||||
|
while self.is_playing.isSet():
|
||||||
|
try:
|
||||||
|
d = self.play_q.get(timeout=.2)
|
||||||
|
self.stream.write(d) # this is a blocking action
|
||||||
|
except queue.Empty as e:
|
||||||
|
# empty play queue leads to a check if the player should run still at all
|
||||||
|
pass
|
||||||
|
logger.info("Finished player")
|
||||||
|
|
||||||
def receive(self, chunk):
|
def receive(self, chunk):
|
||||||
if not self.p:
|
if not self.p:
|
||||||
return
|
return
|
||||||
|
@ -53,12 +77,17 @@ class Player:
|
||||||
data = chunk
|
data = chunk
|
||||||
else:
|
else:
|
||||||
data, self.cv_laststate = audioop.ratecv(chunk, 2, 1, self.src_rate, self.out_rate, self.cv_laststate)
|
data, self.cv_laststate = audioop.ratecv(chunk, 2, 1, self.src_rate, self.out_rate, self.cv_laststate)
|
||||||
self.stream.write(data)
|
|
||||||
|
try:
|
||||||
|
self.play_q.put_nowait(data)
|
||||||
|
except queue.Full as e:
|
||||||
|
logger.log(LOG_BS, "Player queue full")
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
if not self.p:
|
if not self.p:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.is_playing.clear()
|
||||||
self.stream.close()
|
self.stream.close()
|
||||||
self.p.terminate()
|
self.p.terminate()
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ class Recorder:
|
||||||
self.data = array('h')
|
self.data = array('h')
|
||||||
self.currentTranscription = ""
|
self.currentTranscription = ""
|
||||||
|
|
||||||
t = time.strftime("%Y%m%d-%H%M%s")
|
t = time.strftime("%Y%m%d-%H:%M:%S")
|
||||||
|
|
||||||
self.out_folder = os.path.join(self.main_folder, f"{self.hv_id}", t)
|
self.out_folder = os.path.join(self.main_folder, f"{self.hv_id}", t)
|
||||||
if not os.path.exists(self.out_folder):
|
if not os.path.exists(self.out_folder):
|
||||||
|
|
|
@ -104,7 +104,6 @@ class Message(object):
|
||||||
self.variables.sort(key=len, reverse=True)
|
self.variables.sort(key=len, reverse=True)
|
||||||
text = self.text
|
text = self.text
|
||||||
self.logger.debug(f"Getting text for {self.id}")
|
self.logger.debug(f"Getting text for {self.id}")
|
||||||
self.logger.debug(self.variables)
|
|
||||||
for var in self.variables:
|
for var in self.variables:
|
||||||
self.logger.debug(f"try replacing ${var} with {self.variableValues[var]} in {text}")
|
self.logger.debug(f"try replacing ${var} with {self.variableValues[var]} in {text}")
|
||||||
replacement = self.variableValues[var] if (self.variableValues[var] is not None) else "nothing" #TODO: translate nothing to each language
|
replacement = self.variableValues[var] if (self.variableValues[var] is not None) else "nothing" #TODO: translate nothing to each language
|
||||||
|
@ -914,12 +913,13 @@ class Story(object):
|
||||||
# self.logger.warn("Temporary 'fix' -> stop recording")
|
# self.logger.warn("Temporary 'fix' -> stop recording")
|
||||||
# self.hugvey.google.pause()
|
# self.hugvey.google.pause()
|
||||||
|
|
||||||
self.logger.debug("Pending directions: ")
|
logmsg = "Pending directions:"
|
||||||
|
|
||||||
for direction in self.getCurrentDirections():
|
for direction in self.getCurrentDirections():
|
||||||
conditions = [c.id for c in direction.conditions]
|
conditions = [c.id for c in direction.conditions]
|
||||||
self.logger.debug(
|
logmsg += "\n- {0} -> {1} (when: {2}) ".format(direction.msgFrom.id, direction.msgTo.id, conditions)
|
||||||
"- {0} -> {1} (when: {2}) ".format(direction.msgFrom.id, direction.msgTo.id, conditions))
|
|
||||||
|
self.logger.log(LOG_BS,logmsg)
|
||||||
|
|
||||||
def getCurrentDirections(self):
|
def getCurrentDirections(self):
|
||||||
if self.currentMessage.id not in self.directionsPerMsg:
|
if self.currentMessage.id not in self.directionsPerMsg:
|
||||||
|
|
|
@ -3,6 +3,7 @@ import logging
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
import coloredlogs
|
import coloredlogs
|
||||||
import argparse
|
import argparse
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
argParser = argparse.ArgumentParser(description='Start up a Hugvey pillow. Mic stream becomes available on TCP Socket, and starts listening + emitting events')
|
argParser = argparse.ArgumentParser(description='Start up a Hugvey pillow. Mic stream becomes available on TCP Socket, and starts listening + emitting events')
|
||||||
|
@ -24,6 +25,11 @@ if __name__ == '__main__':
|
||||||
default=None,
|
default=None,
|
||||||
help="Force the given id to be used. Must be integer"
|
help="Force the given id to be used. Must be integer"
|
||||||
)
|
)
|
||||||
|
argParser.add_argument(
|
||||||
|
'--log',
|
||||||
|
action='store_true',
|
||||||
|
help="If given, logs are send to cutelog on the central command, if cutelog is not started this could significantly slow down the Hugvey"
|
||||||
|
)
|
||||||
|
|
||||||
args = argParser.parse_args()
|
args = argParser.parse_args()
|
||||||
|
|
||||||
|
@ -33,9 +39,15 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
logger = logging.getLogger("hugvey")
|
logger = logging.getLogger("hugvey")
|
||||||
# logger.setLevel(1) # to send all records to cutelog
|
# logger.setLevel(1) # to send all records to cutelog
|
||||||
socket_handler = logging.handlers.SocketHandler('hugveycmd.local', 19996) # default listening address
|
|
||||||
logger.addHandler(socket_handler);
|
|
||||||
|
|
||||||
hv = Hugvey(args.id)
|
hv = Hugvey(args.id)
|
||||||
hv.loadConfig(args.config)
|
hv.loadConfig(args.config)
|
||||||
|
|
||||||
|
if args.log:
|
||||||
|
host = urlparse(hv.config['events']['cmd_address']).hostname
|
||||||
|
logger.info("Connect to logger on {}".format(host))
|
||||||
|
socket_handler = logging.handlers.SocketHandler(host, 19996) # default listening address
|
||||||
|
logger.addHandler(socket_handler);
|
||||||
|
|
||||||
hv.start()
|
hv.start()
|
||||||
|
|
Loading…
Reference in a new issue