diff --git a/client_config.yml b/client_config.yml index 027c77d..5628853 100644 --- a/client_config.yml +++ b/client_config.yml @@ -5,11 +5,13 @@ voice: input_rate: 44100 target_rate: 16000 port: 4444 - input_name: null - file_address: "http://hugveycmd.local:8888" - play_device: 0 # alsa mixer nr - play_volume: 80 - play_audiodev: "hw:2,0" # sox AUDIODEV + input_name: 'AK5371' + output_name: 'USB Audio Device' + input_mixer: 'Mic' + output_mixer: 'PCM' + input_volume: 90 + output_volume: 60 + file_address: "http://hugveycmd.local:8888" diff --git a/hugvey/client.py b/hugvey/client.py index 40a99b6..1869004 100644 --- a/hugvey/client.py +++ b/hugvey/client.py @@ -36,14 +36,14 @@ def setLogger(hv_id): class VoiceServer(object): """A UDP server, providing mic data at 16 kHz""" - def __init__(self, loop, hugvey, voice_port: int, input_rate: int, input_name: str = None, target_rate: int = 16000): - self.voice_port = voice_port - self.input_rate = input_rate - self.target_rate = target_rate + def __init__(self, loop, hugvey, config): + self.config = config + + self.input_rate = self.config['voice']['input_rate'] + self.target_rate = self.config['voice']['target_rate'] self.stopped = True self.clients = [] self.laststate = None - self.input_name = input_name self.ctx = Context.instance() self.loop = loop self.hugvey = hugvey @@ -52,23 +52,100 @@ class VoiceServer(object): 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): + + self.p = pyaudio.PyAudio() + # wait a sec for the input devices to come up + logger.debug('wait for mic') + time.sleep(3) + logger.debug('done waiting for mic') + + + self.info = self.get_card_info() + + + + def get_card_info(self): + output_device_idx = None input_device_idx = None - # input_device_idx = 6 - # input_device_idx = 0 + devices_count = self.p.get_device_count() for i in range(devices_count): dev = self.p.get_device_info_by_index(i) - if input_device_idx is None and dev['maxInputChannels'] > 0: - if (self.input_name and self.input_name in dev['name']) or \ - (not self.input_name and dev['name'] != 'default'): - input_device_idx = dev['index'] - logger.info("Use device {0}: {1}".format( + if output_device_idx is None and dev['maxOutputChannels'] > 0: + if (self.config['voice']['output_name'] and self.config['voice']['output_name'] in dev['name']) or \ + (not self.config['voice']['output_name'] and dev['name'] != 'default'): + output_device_idx = dev['index'] + logger.info("Use output device {0}: {1}".format( dev['index'], dev['name'])) - logger.debug("{} {:0d} {}".format( - "* " if input_device_idx == i else "- ", i, dev['name'])) - return input_device_idx + if input_device_idx is None and dev['maxInputChannels'] > 0: + if (self.config['voice']['input_name'] and self.config['voice']['input_name'] in dev['name']) or \ + (not self.config['voice']['input_name'] and dev['name'] != 'default'): + input_device_idx = dev['index'] + logger.info("Use input device {0}: {1}".format( + dev['index'], dev['name'])) + logger.debug("{} {:0d} {} (i: {}, o: {})".format( + "< " if output_device_idx == i else "> " if input_device_idx == i else "- ", i, dev['name'], + dev['maxInputChannels'], + dev['maxOutputChannels'])) + +# Don't continue without pyAudio indexes + if input_device_idx is None: + raise Exception("Input device is not found: {}".format(self.config['voice']['input_name'])) + if output_device_idx is None: + raise Exception("Output device is not found: {}".format(self.config['voice']['output_name'])) + + try: + # get eg: "hw:1,0" or "hw:0,3" -> used by Sox' play + output_device_name = self.p.get_device_info_by_index(output_device_idx)['name'].split("(",1)[1][:-1] + # get eg: "hw:1" or "hw:0" -> used by alsaaudio.Mixer(device=..) + output_card_name = output_device_name.split(",",1)[0] + except IndexError as e: + output_device_name = None + output_card_name = None + try: + input_device_name = self.p.get_device_info_by_index(input_device_idx)['name'].split("(",1)[1][:-1] + input_card_name = input_device_name.split(",",1)[0] + except IndexError as e: + input_device_name = None + input_card_name = None + + print(output_device_name, input_device_name) + + + return { + 'input': { + 'idx': input_device_idx, + 'device': input_device_name, + 'card': input_card_name + }, + 'output': { + 'idx': output_device_idx, + 'device': output_device_name, + 'card': output_card_name + } + } + + +# +# def get_output_idxs(self): +# pass +# +# def get_input_idx(self): +# input_device_idx = None +# # input_device_idx = 6 +# # input_device_idx = 0 +# devices_count = self.p.get_device_count() +# for i in range(devices_count): +# dev = self.p.get_device_info_by_index(i) +# if input_device_idx is None and dev['maxInputChannels'] > 0: +# if (self.input_name and self.input_name in dev['name']) or \ +# (not self.input_name and dev['name'] != 'default'): +# input_device_idx = dev['index'] +# logger.info("Use device {0}: {1}".format( +# dev['index'], dev['name'])) +# logger.debug("{} {:0d} {}".format( +# "* " if input_device_idx == i else "- ", i, dev['name'])) +# return input_device_idx def onBuffer(self, in_data, frame_count, time_info, status): if self.input_rate == self.target_rate: @@ -108,13 +185,28 @@ class VoiceServer(object): CHANNELS = 1 CHUNK = 4096 - self.p = pyaudio.PyAudio() + self.stopped = False - # wait a sec for the input devices to come up - logger.debug('wait for mic') - await asyncio.sleep(3) - logger.debug('done waiting for mic') + + if 'alsaaudio' in sys.modules: + if self.config['voice']['input_mixer'] and self.config['voice']['input_volume'] and self.info['input']['card']: + logger.info("Set input volume on {}/{} to {}".format( + self.config['voice']['input_mixer'], + self.info['input']['card'], + self.config['voice']['input_volume'] + )) + alsaaudio.Mixer(self.config['voice']['input_mixer'], device=self.info['input']['card']).setvolume( + self.config['voice']['input_volume']) + + if self.config['voice']['output_mixer'] and self.config['voice']['output_volume'] and self.info['output']['card']: + logger.info("Set output volume on {}/{} to {}".format( + self.config['voice']['output_mixer'], + self.info['output']['card'], + self.config['voice']['output_volume'] + )) + alsaaudio.Mixer(self.config['voice']['output_mixer'], device=self.info['output']['card']).setvolume( + self.config['voice']['output_volume']) stream = self.p.open( format=FORMAT, @@ -123,12 +215,12 @@ class VoiceServer(object): input=True, frames_per_buffer=CHUNK, stream_callback=self.onBuffer, - input_device_index=self.get_input_idx() + input_device_index=self.info['input']['idx'] ) while not self.stopped: try: - address = "tcp://*:{}".format(self.voice_port + self.hugvey.id) + address = "tcp://*:{}".format(self.config['voice']['port'] + self.hugvey.id) self.voice_socket = self.ctx.socket(zmq.PUB) self.voice_socket.bind(address) @@ -334,26 +426,21 @@ class Hugvey(object): logger.debug('Hugvey {}, reporting'.format(self.id)) loop = asyncio.get_event_loop() - - if self.config['voice']['play_device'] and 'alsaaudio' in sys.modules: - alsaaudio.Mixer(self.config['voice']['play_device']).setvolume( - self.config['voice']['play_volume']) - + + self.voice_server = VoiceServer( + loop=loop, + hugvey=self, + config=self.config + ) + self.cmd_server = CommandHandler( hugvey_id=self.id, cmd_address=self.config['events']['cmd_address'], publish_address=self.config['events']['publish_address'], file_address=self.config['voice']['file_address'], - play_audiodev=self.config['voice']['play_audiodev'] - ) - self.voice_server = VoiceServer( - loop=loop, - hugvey=self, - voice_port=int(self.config['voice']['port']), - input_rate=int(self.config['voice']['input_rate']), - input_name=self.config['voice']['input_name'], - target_rate=int(self.config['voice']['target_rate']), + play_audiodev=self.voice_server.info['output']['device'] ) + logger.info('start') # self.voice_server.asyncStart(loop) # loop.run_until_complete(self.voice_server.start())