hugvey/hugvey/speech/player.py

95 lines
3.1 KiB
Python

import pyaudio
import logging
import audioop
import threading
import queue
from hugvey.communication import LOG_BS
mainLogger = logging.getLogger("hugvey")
logger = mainLogger.getChild("player")
class Player:
"""
Play the streamed audio
"""
def __init__(self, src_rate, out_rate):
self.p = None
self.src_rate = src_rate
self.out_rate = out_rate # unfortunately not every device plays 16kHz audio streams
self.cv_laststate = None
try:
self.p = p = pyaudio.PyAudio()
self.stream = p.open(format=pyaudio.paInt16,
channels=1,
rate=out_rate,
output=True,
output_device_index=self.get_output_idx()
)
except Exception as e:
logger.critical(f"Player not instatiated: {e}")
if self.p:
self.p.terminate()
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):
output_device_idx = None
output_device_idx = 14
devices_count = self.p.get_device_count()
for i in range(devices_count):
dev = self.p.get_device_info_by_index(i)
# print(dev)
if output_device_idx is None and dev['maxOutputChannels'] > 0:
output_device_idx = dev['index']
logger.info("Use device {0}: {1} {2}".format(dev['index'],dev['name'], dev['maxOutputChannels']))
logger.debug("{} {:0d} {}".format("* " if output_device_idx == i else "- ", i, dev['name']))
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):
if not self.p:
return
# logger.debug('receive {}'.format(len(chunk)))
if self.src_rate == self.out_rate:
data = chunk
else:
data, self.cv_laststate = audioop.ratecv(chunk, 2, 1, self.src_rate, self.out_rate, self.cv_laststate)
try:
self.play_q.put_nowait(data)
except queue.Full as e:
logger.log(LOG_BS, "Player queue full")
def shutdown(self):
if not self.p:
return
self.is_playing.clear()
self.stream.close()
self.p.terminate()
def triggerStart(self):
pass